行車記+翻車記:.NET Core 新車改造,C# 節能降耗,docker swarm 重回賽道

很是抱歉,10:00~10:30 左右博客站點出現故障,給您帶來麻煩了,請您諒解。html

故障緣由與博文中談到的部署變動有關,但背後的問題變得很是複雜,複雜到咱們都在懷疑與阿里雲服務器 CPU 特性有關。redis

這篇博文原本準備 9:30 左右發佈的,但發佈博文時出現了 docker swarm 部署異常狀況,切換到 docker-compose 部署後問題依舊,一直到 10:30 左右才恢復正常,繼續發佈這篇博文,在標題中加上了「翻車記」。docker

原先的博文正文開始:數據庫

週一貫你們彙報車況以後,咱們的 .NET Core 新車繼續以 docker-compose 手動擋的駕駛方式行駛在信息高速公路上,即便昨天駛上了更快的高速(併發量更大的訪問高峯),也沒有翻車。通過這周3天訪問高峯的考驗,咱們終於能夠充滿信心地宣佈——咱們度過了新車上路最艱難的磨合期,開新車的劇情從「翻車記」進入到了「行車記」。緩存

翻車成爲歷史,行車正在進行時,但離咱們的目標「飆車」還有很長的一段距離,「行車記」更多的是修車記,新車改造記。服務器

目前這輛 .NET Core 新車有2個重大問題,一是油耗高(CPU消耗高),有時還會斷油(CPU 100% 形成 502),二是手動擋駕駛實在太累。併發

針對油耗高問題,這兩天咱們從節能降耗角度對博客系統的 C# 代碼進行了優化。app

從日誌中發現,有些特別長的 url 會形成 ASP.NET Core 內置的 url rewrite 中間件在正則處理時執行超時。負載均衡

System.Text.RegularExpressions.RegexMatchTimeoutException: The RegEx engine has timed out while trying to match a pattern to an input string. This can occur for many reasons, including very large inputs or excessive backtracking caused by nested quantifiers, back-references and other factors.
   at System.Text.RegularExpressions.RegexRunner.DoCheckTimeout()
   at Go64(RegexRunner )
   at System.Text.RegularExpressions.RegexRunner.Scan(Regex regex, String text, Int32 textbeg, Int32 textend, Int32 textstart, Int32 prevlen, Boolean quick, TimeSpan timeout)
   at System.Text.RegularExpressions.Regex.Run(Boolean quick, Int32 prevlen, String input, Int32 beginning, Int32 length, Int32 startat)
   at System.Text.RegularExpressions.Regex.Match(String input, Int32 startat)
   at Microsoft.AspNetCore.Rewrite.UrlMatches.RegexMatch.Evaluate(String pattern, RewriteContext context)
   at Microsoft.AspNetCore.Rewrite.IISUrlRewrite.IISUrlRewriteRule.ApplyRule(RewriteContext context)
   at Microsoft.AspNetCore.Rewrite.RewriteMiddleware.Invoke(HttpContext context)

對於這個問題,咱們採起的節能降耗措施是藉助 AspNetCore.Rewrite 的機制檢查 url 的長度,對超出長度限制的 url 直接返回 400 狀態碼。異步

public class UrlLengthLimitRule : IRule { private readonly int _maxLength; private readonly int _statusCode; public UrlLengthLimitRule(int maxLength, int statusCode) { _maxLength = maxLength; _statusCode = statusCode; } public void ApplyRule(RewriteContext context) { var url = context.HttpContext.Request.GetDisplayUrl(); if (url.Length > _maxLength) { context.HttpContext.Response.StatusCode = _statusCode; context.Result = RuleResult.EndResponse; context.Logger.LogWarning($"The Url is too long to proceed(length: {url.Length}): {url}"); } } }

爲了節約每次請求時建立 DbContext 的開銷,從新啓用了 DbContextPool ,從省吃儉用的角度進一步下降油耗。

services.AddDbContextPool<CnblogsDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("BlogDb"), builder => { builder.UseRowNumberForPaging(); builder.EnableRetryOnFailure( maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(10), errorNumbersToAdd: new int[] { 2 }); }); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); });

限制了一個耗油大戶,有些字符數特別多的博文內容(好比將圖片以 base64string 保存在博文內容中)在正則處理時特別消耗 CPU ,並且 memcached 沒法緩存(之後會改用 redis 緩存解決這個問題),對這些博文采起了限制措施。這是咱們在遷移時本身給本身挖的坑,舊版中已經採起了措施,但在遷移時遺漏了。

另外一個節能降耗措施一樣是針對博文內容,將從數據庫中獲取博文內容的代碼由 EF Core + LINQ 改成 Dapper + 存儲過程,以避開 好大一個坑: EF Core 異步讀取大字符串字段比同步慢100多倍 。在執行 DbCommand.ExecuteReaderAsync 時,EF Core 使用的是 CommandBehavior.Default ,Dapper 使用的是 CommandBehavior.SequentialAccess 。在有些場景下使用 CommandBehavior.Default 查詢很大的字符串,有嚴重的性能問題,不只查詢速度極慢,並且很耗 CPU (也有可能與使用的 SQL Server 版本有關),只要使用 EF Core ,就只能使用 CommandBehavior.Default ,EF Core 沒有提供任何修改 CommandBehavior 的配置能力,因此換成 Dapper 也是無奈之舉。

public async Task<string> GetByPostIdAsync(int postId) { using (var conn = new SqlConnection(GlobalSettings.PostBodyConnectionString)) { return await conn.QueryFirstOrDefaultAsync<string>( "[dbo].[Cnblogs_PostBody_Get]", new { postId }, commandType: CommandType.StoredProcedure); } }

對於手動擋駕駛太累問題,在此次改造過程當中,咱們採起一個被全園人都反對的舉措,沒有安裝衆星捧月的 k8s 高檔自動駕駛系統,而是安裝了小衆的 docker swam 中檔自動駕駛系統。這種「docker swarm 虐我千百遍,我待 docker swarm 如初戀」的情有獨鍾的傻勁,也許是受《try everything》這首歌的影響,咱們仍是想試試在優化後是否可使用 docker swarm 自動駕駛系統在高速上正常開車(抗住訪問高峯),先看看 docker swarm 到底是弱不由風,仍是隻是養尊處優?

爲了照顧 docker swarm 的養尊處優,咱們在代碼中減小一處額外的 HttpClient 形成的 socket 鏈接開銷。在新版博客系統中爲了防止有些地方在遷移時遺漏了,咱們在一個 middleware 中會跟蹤全部 404 響應,並用 404 對應的 url 向舊版博客發請求,若是舊版響應是 200 ,就記錄的日誌中留待排查。在訪問高峯,大量的 404 請求也會帶來很多的 socket 鏈接開銷。

Docker swarm 部署的 .NET Core 博客站點昨天晚上就已經上線觀察了,但昨天是 docker swarm 與 docker-compose 混合部署,今天一大早已經所有換成 docker swarm 部署了,新車以由手動擋駕駛模式切換爲 docker swarm 自動駕駛模式行駛,目前一切情況良好(9:10左右),就看今天上高速的狀況了。

咱們準備了備案,假如 docker swam 在訪問高峯撐不住,隨時能夠切換到手動擋(docker-compose 部署隨地待命)。

-----原先的博文正文結束-----

9:30 左右,剛準備發這篇博文時發現還沒上高速纔剛上快速路 docker swarm 就有點撐不住了(3臺8核16G的阿里雲服務器),趕忙向手動擋切換,當即向負載均衡添加了3臺4核8G的 docker-compose 部署的阿里雲服務器(這3臺在向手動擋切換前就一直處於運行狀態),6臺服務器撐住了。

根據當時的狀況,咱們徹底認爲就是 docker swarm 的問題,是 docker swarm 弱不由風,docker swarm 是一個低檔的自動駕駛系統,沒法用它在高速上開車(如今來看不必定是 docker swarm 的問題)。因而,咱們進行進一步的切換,將處於關機狀態的另外4臺 docker-compose 部署的服務器開起來加入負載均衡,將 docker swarm 的服務器摘下負載均衡並關機,這時負載均衡中有7臺4核8G的 docker-compose 部署的服務器,按照前幾天的狀況看,徹底能夠撐住。可是,萬萬沒有想到,從 10:00 左右開始,這7臺居然也撐不住,並且問題表現與以前 docker swarm 遇到的問題同樣,部分服務器本機請求時快時慢,快的時候在10毫秒左右,慢的時候請求執行時間超過30秒,甚至超時。趕忙繼續加服務器,但這時加服務器須要購買、啓動、預熱,雖然是腳本自動完成的,但也比較慢,加了服務器後,問題依舊,因而將一些出問題的服務器下線,但會有其餘服務器又出現這個問題,即便新加的服務器也會出現這個問題,在一邊加服務器一邊將出問題的服務器下線的同時,將 docker swarm 集羣的3臺服務器也啓動起來加入集羣分擔壓力,但很快 docker swarm 集羣中的部分服務器也出現了一樣的問題。。。

10:30 左右,當達到某種咱們所不知道的平衡點時,當即風平浪靜,一切都回歸正常,全部服務器本機器本機請求都飛快,包含 docker swarm 集羣中的服務器。

如今問題變得格外複雜,回想以前的翻車與正常行駛的狀況,從直覺判斷中彷佛感受到了一點點新的蛛絲馬跡,一個咱們從沒懷疑的點可能要歸入考慮範圍 —— 阿里雲服務器 CPU 的性格特色。接下來,咱們會仔細分析一下,看能不能找到一點規律,按照比較符合阿里雲服務器 CPU 性格特色的方式接入負載,看是否能夠避開這個問題。

再次抱歉,給你們帶來這麼大的麻煩,請諒解。此次故障咱們萬萬沒有想到,高速開車比咱們想象的難不少,即便一樣的部署,接入負載或者增長服務器的時間點不同,也會有不同的表現。

 

Powered by .NET Core 系列博文:

園友相關博文:

原文出處:https://www.cnblogs.com/cmt/p/11391410.html

相關文章
相關標籤/搜索