两年来的core折腾之路几点总结,附上nginx启用http2拿来即用的配置
為什么要遷移,江湖上傳說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規范,少來,雞毛規范和我一點關系沒有,我只認識過度的自作聰明是找罵。
| 1 2 3 4 5 6 7 8 9 | 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,什么亂七八糟的功能都用上,但是同時也造就了遷移過程中一道巨大的壁壘,解決辦法有三:
只遷移應用程序,不遷移數據庫,但是如果說應用程序運行在Linux,卻依然依賴于Windows上的SQLServer的話,對于系統的遷移目的來說,是掩耳盜鈴。
Linux版SQLServer,且先不說它目前是RC,重點依然是個人原因,需要一臺4G內存的主機,窮,買不起。當年谷歌通過廉價PC構造集群的思路,始終值得學習。
【采用】重構數據庫架構,什么存儲過程、地理編碼、表間關聯…… 通通不要了(個人問題,不具備參考價值,請勿仿效),由應用程序來代替之,數據庫只保留最基本的表存儲、最基本的數據類型、做到最大的兼容性,于是現在服務器A基于SQLServer、服務器B基于SQLite、服務器C基于MySQL、它們共用相同的應用程序、分布在三個地理位置,組成API集群,定時相互同步數據,讓各種各樣的前端服務器、移動端應用程序根據地理位置的變化、實時熱切換數據源,同時使用三個不同的數據庫來運作,是出于實驗目的,打算再觀察幾個月,看看它們分別有什么表現,為以后能否在經濟上優化作參考。
同時,幾臺www服務器組成另一個集群,處理各種客戶端的交互,包括與APP交互,www集群節點也具備集群中某一節點崩潰時自動重建功能,但是不與數據庫直接打交道,只與API集群交互,不可感知API服務器的再后端使用了什么數據庫。如此就具備了熱切換API節點的能力,為什么要構造“兩道轉輪”式的架構呢,因為頻繁的功能更新、BUG修復,只要保證通信協議的不變,無論是WWW集群或API集群中任一節點下線,都不會影響整套系統的運行,域名的DNS服務層面上監視著WWW集群的健康狀態,最終客戶正在請求的某個www節點下線時,DNS服務器可感知,便會實時把最終客戶的域名請求調度到下一個健康的www節點的IP,而www集群所有節點也記錄了所有api集群節點的地址,可以在當前與API的通信崩潰時自動篩選到下一健康節點嘗試連接,篩選的過程是地理位置優先,然后是健康狀態。它們跨洋分布在三個國家,除非三個國家都同時停電,否則系統將會完好運行,最終客戶不會感知到變化。
?
下面是我用一段JSON偽代碼來描述它們之間的關系,注意顏色對應,x表示會變動的下標:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | { ????? 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不用說了吧。我在博客園同樣也上傳有很多照片,不過請站長放心,我沒干那事,以后也不打算干。
淘寶圖片空間:總有一天它會關閉或者加某些限制的,所以目前已經不再添加新的東西進去了,但是在有些頁面上,還在鏈接著,懶,有空再改。
【目前的方案】自建文件服務器,目前它們分布在浙江、武漢、山東、成都、美人希,www服務器會根據請求者的地理位置,返回 <img src="https://最近的文件服務器IP" />,依然是基于成本考慮,國內這四個地區服務器的總運營成本每年2000塊錢左右,而且免流量。而在國外的服務器基于azure,也是買的最廉價版本,每月14.88美元,按照7的匯率換算,約104每月,每月1T流量,看似還是不夠,但是要知道在網絡zi-you區,綁定cloudflare的免費cdn就行了。 國內的四個文件服務器直接請求IP,反正img的src是動態的,一來免去了那道你懂的手續,二來沒有dns,也相對的提升了速度。當然也留有一手后路:一個總開關統一請求美人希(反正那時候就不是快與慢的問題了)。這幾個文件服務器之間也自發性的組成一個集群,它們會相互同步文件,壞了沒關系,集群中的第一個感知到的節點會通過API嘗試重啟,三次重啟失敗的話會銷毀舊的,同時建立一個新的服務器,然后再自動同步。
這下知道我為什么要把代碼放到linux了吧,便宜,你仔細找還是可以找到的,windows不可能有這價錢,壞了拉倒,立即重建,整個自動化重建過程不到5分鐘。重建后服務器間的同步填充,通過http2的流式連接,快得很。而通過core的selfhost編譯出來的應用程序,你的裸機根本不需要裝運行時,sftp傳上去就立即啟動運作,azure上就更方便了,我之前建好iso,基于iso啟動就完事,傳程序都免了,只需同步內容數據。
說到這,又驗證了一個道理:這個世界,只分為兩處:墻內和墻外。
這樣一來每年總的運營成本3400塊錢左右,基本上不需要再為流量操心。當然,光是這么做還不夠,為了緩解瞬間并發壓力,客戶端緩存是必不可少的,Cache-Control 的 max-age 設置了盡可能的大,反正二進制文件不可能更改的,要改也是改img的src。同時,也通過http2來優化連接效率,關于http2,以下是我給出的配置,從一臺centos 7.1裸機起步,假設已經是root:
暫時利用一下nginx:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 添加: /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的例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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來做反向代理,否則我也不愿意折騰這事情,目前也正在想法怎么可以拋棄前端代理,現在思路是:
合并兩個服務的代碼,kestrel就只有一個了。
分離到兩臺服務器,成本翻倍,窮。
等你補充
為了整套系統的全面控制,這事情是一定要做的,而且我也不相信前端服務器必備的論點,什么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文件來發布,反正以后切換平臺也是命令行編譯發布,也就無所謂了。
原文地址:http://www.cnblogs.com/kvspas/p/dotnet-core-2years-by-liangyichen.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的两年来的core折腾之路几点总结,附上nginx启用http2拿来即用的配置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Standard 2.0:整齐
- 下一篇: 微软的FreeBSD社区推广活动 北京站