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

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

非常抱歉,10:00~10:30 左右部落格站點出現故障,給您帶來麻煩了,請您諒解。

故障原因與博文中談到的部署變更有關,但背後的問題變得非常複雜,複雜到我們都在懷疑與阿里雲伺服器 CPU 特性有關。

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

原先的博文正文開始:

週一向大家彙報車況之後,我們的 .NET Core 新車繼續以 docker-compose 手動擋的駕駛方式行駛在資訊高速公路上,即使昨天駛上了更快的高速(併發量更大的訪問高峰),也沒有翻車。經過這周3天訪問高峰的考驗,我們終於可以充滿信心地宣佈——我們度過了新車上路最艱難的磨合期,開新車的劇情從“翻車記”進入到了“行車記”。

翻車成為歷史,行車正在進行時,但離我們的目標“飆車”還有很長的一段距離,“行車記”更多的是修車記,新車改造記。

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

針對油耗高問題,這兩天我們從節能降耗角度對部落格系統的 C# 程式碼進行了優化。

從日誌中發現,有些特別長的 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 系列博文:

  • 【故障公告】釋出 .NET Core 版部落格站點引起大量 500 錯誤
  • 【網站公告】.NET Core 版部落格站點第二次釋出嘗試
  • 暴風雨中的 online : .NET Core 版部落格站點遭遇的高併發問題進展
  • Powered by .NET Core 進展:驗證高併發效能問題嫌疑犯 docker swarm
  • 同“窗”的較量:部署在 Windows 上的 .NET Core 版部落格站點發布上線
  • 做夢也沒有想到:Windows 上的 .NET Core 版部落格系統表現更糟糕
  • 峰迴路轉:去掉 DbContextPool 後 Windows 上的 .NET Core 版部落格表現出色
  • Powered by .NET Core 進展0815:第5次釋出嘗試(Windows部署)
  • 高速開車換底盤記:Windows 與 Linux 部署都抗住了,但修車任務艱鉅
  • Powered by .NET Core 進展0819:高速開車車況彙報 

園友相關博文:

  • 部落格園升級有感一點建議
  • 部落格園翻車啟示錄
  • 生產環境(基於docker)故障排除? 有感於部落格園三番五次翻車
  • 部落格園在升級的路上,不妨更自信些,同時說說我們可以為部落格園做些什麼