只有累積,沒有奇蹟

2019年9月12日 星期四

[NETCore] ASP.NET Core Task block 檢測器 - Ben.BlockingDetector

前言
微軟從 ASP.NET Framework 4.5 開始支援 async / await 語法,提供開發者在撰寫非同步作業時更輕鬆與容易上手,如果對於 async / await 不夠熟悉可以看黑暗大之前的好文 ASP.NET async 基本心法但有時在一知半解寫出來的代碼有時殺傷力才會是最可怕的,如果沒有正確使用 async / await 會造成 Thread block 的問題發生,因此在撰寫 async / await 時需要更加小心留意,這篇文章就來介紹最近發現的 Ben.BlockingDetector 套件來解決此問題若有問題或是錯誤的地方歡迎高手大大給予指導或討論

安裝
為了讓大家更容易了解建立一個範例專案,專案範本使用 ASP.NET Core API 專案
開啟 Visual Studio 的 Nuget 並搜尋 BlockingDetector下載 
或是可以透過 Nuget Package Console 輸入下列指令
Install-Package Ben.BlockingDetector -Version 0.0.3
安裝完畢之後到專案檔底下確認是否有安裝成功
<ItemGroup>
  <PackageReference Include="Ben.BlockingDetector" Version="0.0.3" />
</ItemGroup>

使用
一開始有提到過 async / await 未正確使用會造成 Thread block 情況發生,這裡舉一個最近遇到的 case 來說明,公司同仁連續幾天反應網站緩慢已嚴重影響正常操作,我們使用記憶體分析工具分析 dump 出來的 memory 檔案後,發現報告內容多個 Thread 都在等待的狀態,再繼續查看 Thread 的 Call Stack 發現緩慢的程式進入點皆為 PostJsonAsync,找到執行的代碼中發現是使用 Task.Result 要求同步方式回傳結果
在看到使用 Task.Result 幾位資深的同仁都笑了,紛紛表示都有踩過這雷,詳細造成原因與細節說明可以參考黑暗大的文章 : await與Task.Result/Task.Wait()的Deadlock問題,文中介紹來龍去脈非常清楚這裡就不在另外說明,在非同步中常見造成 block 原因有下列兩種

  • Task.wait
  • Task.Result

  • 解決方案有兩種,第一種是加上  configureAwait(false)  指定任務完成之後不再使用/等待原本的 Thread 執行,另一種則是將呼叫方法也改為 async 非同步方式進行,這兩種方法都能有效改善 block 問題。但此作法僅能在嚴重影響到應用程式運行時才能發現,在專案中如果要避免 Task block 情況發生,可以使用今天要介紹的主角 Ben.BlockingDetector 幫我們達到此目的,Ben.BlockingDetector 套件將會於 CLR 檢測 lock,ManualResetEventSlim,Semaphore{Slim},Task.Wait,Task.Result 等情況,並在遇到時紀錄在 Log 中,Log 內容會記錄 "Blocking method has been invoked and blocked, this can lead to threadpool starvation.",方便開發團隊日後進行追蹤的動作,下面將介紹使用方式

    加入 Detector
    開啟剛剛建立好的範例專案,在 Startup.cs 中 Configure 加入  app.UseBlockingDetection();  代碼
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseBlockingDetection();
        // other 
    }

    調整代碼
    為了要示範 block 時的 log,我們調整 ValuesController 中 Get 方法加上 Task.Result 代碼,希望模擬阻塞的程式碼
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public async void Get()
        {
            var result = await Task.Run(() => BlockingTask());
        }
        private static int BlockingTask()
        {
            // Detected blocking
            return MethodAsync().Result;
        }
    
        private static async Task<int> MethodAsync()
        {
            await Task.Delay(1000);
            return 5;
        }
    }
    接著啟動專案後並觀察輸出的 console 紀錄,可以清出的看到啟動後就有記錄到 block 的情況發生
    透過以上的步驟可以發現使用相當的簡單,套件實際背後運作原理相信對 ASP.NET Core 有一定理解的人也可以想像到,是加上 middleware 層針對執行的 thread 進行 monitor 的動作,若有興趣了解細節運作可以查看其 Github source code : 傳送門

    感想
    從上列描述可以得知使用上相當的簡單,下一步就可以看開發者要如何 "處理" 監測到的潛在風險代碼,或是將此紀錄丟到 ELK 加入 Daskboard 定期進行追蹤,發現的時候再請同仁協助改掉避免 block 情況影響到應用程式的運作,就看各位開發者大大如何處置了,希望這篇文章有幫助到大家&喜歡 :)

    參考
    Ben.BlockingDetector

    0 意見:

    張貼留言

    Copyright © m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com