程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 兩年來的core折騰之路幾點總結,附上nginx啟用http2拿來即用的配置,nginxhttp2

兩年來的core折騰之路幾點總結,附上nginx啟用http2拿來即用的配置,nginxhttp2

編輯:關於.NET

兩年來的core折騰之路幾點總結,附上nginx啟用http2拿來即用的配置,nginxhttp2


序:一年多沒更新博客園的內容了,core已經發生了翻天覆地的變化,想起2014年這時候,我就開始了從當時還叫k的那套preview都不如的vnext搭建這套系統,陸陸續續它每一次升級,我也相應地折騰,大約4個月前,我開始把生產環境的一部分從 windows server 遷移到 centos 7 上,觀察了幾個月,覺得可以全面遷移了,於是總結了折騰的路上幾點經驗,與大家共勉。雖說我今天早已不是全職程序員,但是這套系統在我有空的時候總會維護與更新,它的運作與我目前的工作相輔相成,並且會一直更新下去。

這系統干什麼的及地址就不說了,免得人家說我這不是碼農的碼農還賴在博客園發軟文。

 

為什麼要遷移,江湖上傳說windows server的穩定性不如某某某,這類議題與八卦新聞沒兩樣,不談,如果windows的價錢能夠和linux相同或者差異不至於那麼大,我才懶得換,因為窮,這才是重點。

涉及IO路徑拼接,一定要Path.Combine, 反正我自己眼力比較差,手工拼接的話,有時候多一個或少一個斜槓,或者斜槓方向反了,Linux系統都會出錯,搞半天不知道錯在哪,如果大小寫都搞錯卻沒注意或者一時不知的話,神仙都救不了。

注意用System.Environment.NewLine代替硬編碼的換行符,String.Empty代替"",這兩個看不見的東西,發作的時候喝腦白金都沒用。

而core在初始化的時候,會遍歷wwwroot文件夾包含的所有內容,在windows中無所謂你文件夾怎麼命名,在中文centos也無所謂你文件夾怎麼命名。但是,如果你的生產服務器是英文系統,恰好在wwwroot中有中文文件夾的話,你都不知道為什麼會全局報錯。關於中文文件夾(在某些時刻會演變為亂碼文件夾),我也不知哪裡來的,有些客戶端編輯器組件,就是包含了非英文的文件夾。所以搭建虛擬機測試環境的時候,guest系統需要安裝英文版本來驗證。

newtonsoft的json在反序列化枚舉類型的時候,得到的是名,微軟自家的json在反序列化枚舉的時候,得到的是值,雖然可以通過特性來定義,但是本人對特性相當反感,全套代碼不會用也不可能會用,所以有時候需要用class來代替enum。

Microsoft.AspNetCore.NodeServices 是個好東西,就簡單來說,正則表達式驗證庫,只寫一次,客戶端和服務端都可以共用。

Program.Main方法裡,可以建立後台線程,裡面放一個全局靜態的ConcurrentQueue<Action>,while(true)定時監視它,今天我們編寫的是進程級的asp.net,你可以讓它做很多IIS做不到的真正異步,比如說記錄某些日志,扔action進去就是了,不必等待。

事情還可以做得更絕一些:開辟新進程,把代價高昂的、不確定是否會出錯的、卻需要立即清理內存的操作丟給它,操作完了自動關閉,在這一層面上,你可以藐視.net的垃圾回收。

如果你實在玩不來gulp也沒關系,裝個BundlerMinifier就行了,右鍵選幾個js後自動壓縮

能用PocoController的時候,盡量用,比如說某些頻繁的API,它的資源占用最小。

為HttpClient建立池,static ConcurrentQueue<HttpClient>,兩三個就夠了,一般不會出錯,但是誰也不能保證網絡請求,錯了一個就讓下一個來代替,然後把出錯的扔到上面的ConcurrentQueue<Action>中讓它愛什麼時候銷毀什麼時候銷毀。

文件級數據庫,比如說SQLite,也需要ConcurrentQueue<Action>來封裝寫入邏輯,絕對的保證同一時刻只有一個線程寫,同時做好定時自動備份並且是每小時、每半天、每天、每星期這樣的多個備份,否則不可預料的IO把你的數據庫搞壞,喊天天不應。

總會遇到有人和你勸說XXX是世界上最好的語言,遇到這些事情我不會爭論的,給他一份最新的島國女演員名單就可以了。

windows上你不能覆蓋一個正在運行中的dll,但是linux上可以,先覆蓋,再重啟,但是如果連重啟都不允許的話,就開啟新的端口,讓前段服務器熱切換,nginx的reload命令是實時的,如果你連前nginx也沒有,直接kestrel面向公眾,卻又不允許一秒鐘以上的暫停服務,那就新建服務器來等待域名DNS切換吧。

傳統ASP.NET的Bundle功能現在見不到了,需要在開發階段手動通過gulp來壓縮,但是如果遇到需要實時構造js的情況怎麼辦? 現在既然有了NodeServices,服務端壓縮在理論上不是問題,目前我還沒實現,正在研究。

別折騰與XML相關的東西,凡是XML做的事,都可以用JSON來代替,光是頭部一大車的uri垃圾代碼與脫褲子放屁的namespace兩項,足以讓我徹底拋棄它。

該緩存的東西,用內存來緩存,比如說某些不可能上萬條目的目錄, 建一個靜態字典才1M不到,卻可以少做一次inner join。

該不該內聯javascript,對此我分兩個階段,項目初期是內聯幾句最簡單的代碼,動態生成script標簽來請求真正需要的javascript,然後把內容記錄到本地indexedDB中,以後再也不請求js代碼了,從本地數據庫中讀即可,head加一個meta標簽來標識javascript是否需要重新讀取,css也是同樣的道理,如此一來內聯的代碼也就十多行,這價錢花得起。到了第二階段,內聯和本地數據庫也省了,因為有了http2,服務端推送結合max-age,一次TCP連接也是花費得起的(注意這裡說的是TCP,  TCP!),它同樣是僅一次真正傳輸內容。

關於 public async Task<IActionresult> xxxx() ,此異步非彼異步,具體就不詳解了,請自行搜索。有時候我們需要在一個請求上下文中同時做到等同於桌面應用中的“真異步”,你就不要await,雖然visual studio會給出綠色提示說讓你為某個task加上await,加上的話你才是錯了。而是應該TASK的start()後干別的事情,在方法最後統一waitall。當然此方式涉及到線程開銷,要根據具體問題來取捨。

可能你會需要這一段代碼,否則MVC自帶的 return Json(某對象) 會被強制修改為小寫,據說那是JSON規范,少來,雞毛規范和我一點關系沒有,我只認識過度的自作聰明是找罵。

            services.AddMvc().AddJsonOptions(opt =>
            {
                var resolver = opt.SerializerSettings.ContractResolver;
                if (resolver != null)
                {
                    var res = resolver as DefaultContractResolver;
                    res.NamingStrategy = null;  // <<!-- 修正默認JSON全部變成小寫的問題
                }
            });

同理,關閉一切殺毒軟件,包括windows defender,我就遭遇到windows defender要求上傳一份sqlite的操作代碼到它的服務器作分析,而這份代碼是來自最新的core 1.1,又不是我寫的,傳或不傳呢?這不重要,重要的是我因此把windows defender永久禁用。還好它沒刪東西,但是換做別家,可能就沒這麼好說話了,到時你的kestrel一直報500都不知道為什麼。

通過某時間段的請求頻率確定惡意刷新後,立即永久屏蔽IP,IP列表保留在內存中,Configre的app.UseMiddleware階段就進行判斷,10M的IP列表,加上集群負載均衡,壓力再大的話請求azure的防火牆加持,這一切都應該是程序進行(azure有相應API),看誰玩得狠。

注意檢查服務器日志,某些阿三搜索引擎、自诩為你安全著想的防火牆、三流雲監測、等等,會加大你的服務器壓力,從IP和UserAgent上兩方面屏蔽它們。

注意識別請求路徑,比如說遇到請求“/phpmyadmin”的,絕對是加入永久屏蔽IP列表。

(個人問題,不具備參考價值,請勿仿效):數據庫架構盡可能簡單,從前基於SQLServer,什麼亂七八糟的功能都用上,但是同時也造就了遷移過程中一道巨大的壁壘,解決辦法有三:

同時,幾台www服務器組成另一個集群,處理各種客戶端的交互,包括與APP交互,www集群節點也具備集群中某一節點崩潰時自動重建功能,但是不與數據庫直接打交道,只與API集群交互,不可感知API服務器的再後端使用了什麼數據庫。如此就具備了熱切換API節點的能力,為什麼要構造“兩道轉輪”式的架構呢,因為頻繁的功能更新、BUG修復,只要保證通信協議的不變,無論是WWW集群或API集群中任一節點下線,都不會影響整套系統的運行,域名的DNS服務層面上監視著WWW集群的健康狀態,最終客戶正在請求的某個www節點下線時,DNS服務器可感知,便會實時把最終客戶的域名請求調度到下一個健康的www節點的IP,而www集群所有節點也記錄了所有api集群節點的地址,可以在當前與API的通信崩潰時自動篩選到下一健康節點嘗試連接,篩選的過程是地理位置優先,然後是健康狀態。它們跨洋分布在三個國家,除非三個國家都同時停電,否則系統將會完好運行,最終客戶不會感知到變化。

 

下面是我用一段JSON偽代碼來描述它們之間的關系,注意顏色對應,x表示會變動的下標:

{

     client: {member:[html-web, winform, ios, android], request:wwwserver[x]},

     wwwserver:
     [
         node1:{kestrel1:{server:www, request: apiserver[x]}, kestrel2:fileserver}, 
         node2:{kestrel1:{server:www, request: apiserver[x]}, kestrel2:fileserver},
         node3:{kestrel1:{server:www, request: apiserver[x]}, kestrel2:fileserver}
     ],

     apiserver:
     [
         node1:{database:sqlite},
         node2:{database:sqlserver},
         node3:{database:mysql}
     ]
}

下面再來說說文件服務器,它們是第三層集群,也就是上面偽代碼中的fileserver:

因為我的服務器中有大量的照片,它們的流量是個很大的問題,說要租CDN嗎,1T流量一年300塊錢左右,平均下來每個月850M,我某個主題就包含200多張照片,要花50M的流量,為什麼這麼大,過去1280寬度的屏幕,照片就得2560寬度,然後CSS縮小50%,通過這樣間接的超采樣,才能照顧retina。這 850M/每月 夠塞幾次牙縫?加流量吧,10T每年需要3000塊錢,而這價錢可以買上好幾台性能不錯的服務器了,而且免流量。而且10T還不一定夠,再加? 窮。 所以在很久以前的最前期,我弄了個投機辦法,但不是長久之計:淘寶賣家圖片空間,支持外鏈,它分布全國的cdn不用說了吧。我在博客園同樣也上傳有很多照片,不過請站長放心,我沒干那事,以後也不打算干。

這下知道我為什麼要把代碼放到linux了吧,便宜,你仔細找還是可以找到的,windows不可能有這價錢,壞了拉倒,立即重建,整個自動化重建過程不到5分鐘。重建後服務器間的同步填充,通過http2的流式連接,快得很。而通過core的selfhost編譯出來的應用程序,你的裸機根本不需要裝運行時,sftp傳上去就立即啟動運作,azure上就更方便了,我之前建好iso,基於iso啟動就完事,傳程序都免了,只需同步內容數據。

說到這,又驗證了一個道理:這個世界,只分為兩處:牆內和牆外。

這樣一來每年總的運營成本3400塊錢左右,基本上不需要再為流量操心。當然,光是這麼做還不夠,為了緩解瞬間並發壓力,客戶端緩存是必不可少的,Cache-Control 的 max-age 設置了盡可能的大,反正二進制文件不可能更改的,要改也是改img的src。同時,也通過http2來優化連接效率,關於http2,以下是我給出的配置,從一台centos 7.1裸機起步,假設已經是root:

暫時利用一下nginx:

添加:/etc/yum.repos.d/nginx.repo
內容:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

yum install nginx  #目前是1.10.2,先讓它自動安裝。重點是它會替你去折騰各種配置。

然後:

yum install vim wget lsof gcc gcc-c++ bzip2 -y
yum install net-tools bind-utils -y
yum install expat-devel
yum install git
wget http://sourceforge.net/projects/pcre/files/pcre/8.36/pcre-8.36.tar.gz
wget http://zlib.net/zlib-1.2.8.tar.gz
wget https://www.openssl.org/source/openssl-1.0.2h.tar.gz
wget http://nginx.org/download/nginx-1.10.2.tar.gz

tar xzvf pcre-8.36.tar.gz
tar zxf openssl-1.0.2h.tar.gz
tar xzvf zlib-1.2.8.tar.gz
tar xzvf nginx-1.10.2.tar.gz

git clone git://github.com/yaoweibin/ngx_http_substitutions_filter_module.git
git clone https://github.com/arut/nginx-dav-ext-module
git clone https://github.com/gnosek/nginx-upstream-fair
git clone https://github.com/openresty/echo-nginx-module


cd nginx-1.10.2

./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-file-aio --with-threads --with-ipv6 --with-http_addition_module --with-http_auth_request_module --with-zlib=../zlib-1.2.8 --with-pcre=../pcre-8.36 --with-openssl=../openssl-1.0.2h --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_ssl_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic' --add-module=../echo-nginx-module --add-module=../nginx-upstream-fair --add-module=../nginx-dav-ext-module --add-module=../ngx_http_substitutions_filter_module

make && make install

 

現在就可以按照你自己的需求配置nginx了。下面是https的配置:

yum -y install git bc
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

/opt/letsencrypt/letsencrypt-auto --help

systemctl stop nginx

/opt/letsencrypt/letsencrypt-auto certonly --standalone -d example.com -d www.example.com -d foo.example.com # -d後面是你的各種域名,注意修改

systemctl start nginx

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

 

到此,你的nginx已經可以支持http2和http2了。只強調兩點:1,openssl 必須是 1.0.2h 或者更高,2,如果你的服務器身在牆內,yum下載各種包的時候,有可能會遇到網絡問題,這不是你的錯。

哪怕你不需要http2,但是你一定會需要https,從2017年開始,chrome會對普通http提示“這是不安全網絡”,有些心理毛病的客戶會據此猜測你的網站有病毒、甚至還會演變為你的網站會盜他支付寶的錢、甚至他家的雞少下一個蛋都會說你網站有輻射,你會去和這類人解釋什麼叫ssl嗎?我是不會的。 蘋果AppStore不再接受請求http的app發布,意味著如果你的服務器同時是app的api,就必須升級。

 

下面是一段server的例子:

    server {

         listen 443 ssl http2;
        server_name www.*;
         ssl_certificate /etc/letsencrypt/live/你的具體路徑/fullchain.pem;
         ssl_certificate_key /etc/letsencrypt/live/你的具體路徑/privkey.pem;
         ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
         ssl_prefer_server_ciphers on;
         ssl_dhparam /etc/ssl/certs/dhparam.pem;

         ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
         ssl_session_timeout 1d;
         ssl_session_cache shared:SSL:50m;
         ssl_stapling on;
         ssl_stapling_verify on;
         add_header Strict-Transport-Security max-age=15768000;

        location / {

            proxy_pass http://你的kestrel的實際運行地址;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host; 

        }
    }

 

 

暫時先總結到此,文中的azure指另一個世界的azure。至於鎮內的四台服務器可隨時亂搞,愛怎麼換就怎麼換。

鎮內的運營商有時候不得不做一些違背經營之道的事,也是迫不得已,體諒他們的同時,自己也不得不留一手後備之路。也幸虧他們,從前一個單機web小系統,演變為今天如此復雜的分布式。

 


目前還待解決的問題

1: 為什麼我說要暫時利用一下nginx, 雖然說kestrel自身也可以承載https,但是目前每一個服務器節點上都運行著兩個kestrel實例,如果讓它們直接面向公眾,就出現端口沖突問題,所以也必須通過nginx來做反向代理,否則我也不願意折騰這事情,目前也正在想法怎麼可以拋棄前端代理,現在思路是:

為了整套系統的全面控制,這事情是一定要做的,而且我也不相信前端服務器必備的論點,什麼DDOS,歡迎來搞。

 

2:TypeScript的服務端運行,通過Google搜索:  typescript run on server  ,你會發現很多有趣的討論,為了更好的融合客戶端與服務端。

3:GPU計算一直是我向往的事情,在我認為,Core的優勢不僅僅是跨平台,“進程級”才是它的重點,目前我們處於進程級,就可以操控GPU,而且Azure已經提供了搭配Tesla處理器的主機,而目前我的這套系統也與地理坐標有著業務關聯,待我收集多一些數據,再來開啟這套技術怎麼應用。

4:僅存於疑問階段:根據P2P打洞原理,服務器探測到多個請求者存在於同一個相距很近的地理區域時,互相介紹兩個請求者建立連接,然後這兩個請求者之間互相同步內容數據,這一切都是基於javascript+indexedDB來實現。前提是他們都在打開某個不會短時間內關閉的連接。反正還待研究。

5:目前我還卡在windows的visual studio上,是因為項目庫用的是visual studio online 的 git 同步備份代碼,明明兩個月前我還在mac的vs code上可以同步代碼,現在我在非windows visual studio的一切git客戶端都登不上去了,包括windows版的vs code也登不上,不是網絡問題,我用戶名密碼也沒錯,連接VPN也是一樣,我搜了一下說要在項目主頁裡面尋找一個叫Secret什麼的開關,我找到了,開了也沒用,而且我也沒動過這些相關的東西,如果是一開始就不行,我也就認了,現在是中途不讓登了,不是我的錯,搞了半小時不搞了,以後有時間自建服務器。

6:升級到1.1後,visual studio可以編譯,但是不讓發布了,不知原因,輸出界面沒有任何提示,反正就是點擊了發布之後,瞬間完成動作,實際卻什麼都沒做。 現在通過自建命令行cmd文件來發布,反正以後切換平台也是命令行編譯發布,也就無所謂了。

 


 

我靠,為了做文中的幾個截圖,多開了幾個浏覽器,再次回到這裡後台編輯的時候,edge重新加載了,第一次遇到這類事情,幸虧博客園後台有自動保存,嚇出一身阿富汗。

最後,既然azure支持上傳iso搭建自定義系統,那麼還請各位推薦一款資源最小的linux,我想研究一下能不能把core放上去。

 

今天我看到一則新聞:佳能參與日本宇航局的火箭研發。 時代已經進步到這份上了,奧巴馬也快下台了,而你還在憂心asp.net的第一次預熱? core沒有這概念。這就又說回我在張善友兄的某篇博文的評論中給某位仁兄的回復,某些人必須等到2028年才會相信core可以做事(可能還不夠,2048吧),人的觀點都是長久形成並固化的,就像XXX是世界上最好的語言一樣,不必勸,也不必爭,但是如果你不是這一類,現在就可以開始:進程級、跨平台、開源自定義。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved