1. 程式人生 > >放棄等待,故障到來:少一個 await 引發的 tcp 連線洩漏故障

放棄等待,故障到來:少一個 await 引發的 tcp 連線洩漏故障

今天上午的故障之後,我們 review 了程式碼,通過壓力測試重現問題,分析驗證,最終找到了問題的真正原因 —— 在 ASP.NET Core 程式中呼叫 async 方法時沒加 await 。

public async Task<IActionResult> GetRecommDocuments()
{
    //...
    ShowItem(docs, app); //async方法,其中用到了HttpClient
    return Ok(docs);
}

就是上面的程式碼中“漏”寫了 await ,不是粗心漏寫,是故意為之。我們不關心呼叫 ShowItem 的結果,只要能執行就行,即使執行失敗也可以接受。不加 await 可以讓 GetRecommDocuments 方法呼叫 ShowItem 之後無需等待繼續執行從而提高響應速度,但是沒想到這一招偷工減料,竟然抽空了伺服器的 TCP 連線資源,讓整個伺服器大廈轟然倒塌。

開始從 async/await 的角度怎麼也想不通 —— 少一個 await 怎麼會如此嚴重後果,後來突然想到罪魁禍首不在 async/await 而在依賴注入(DI)。GetRecommDocuments 中的 HttpClient 例項是通過建構函式的 IHttpClientFactory 注入的,注入的 HttpClient 例項的生命週期是 Scoped (當前請求範圍),本來正常情況下一個請求處理結束,HttpClient 例項會被 DI 容器 Dispose,所使用的 Socket 連線會被放回連線池,但是由於沒加 await ,讓 ShowItem 不走正常路,DI 容器在請求處理結束時跟蹤不到 ShowItem 中的 HttpClient 例項,也就不會進行 Dispose 。結果來一個請求,IHttpClientFactory 就建立一個 HttpClient 例項,每個 HttpClient 例項都佔用一個新的 TCP 連線,直至拖垮伺服器。

隨著軟體開發技術的發展,開發效率越來越高了,要寫的程式碼越來越少了,但要考慮的問題卻越來越多了。雖然我們每天都在用著 .NET Core ,但由於其內部工作機制不夠熟悉,原以為“巧奪天工”的一招卻釀成大錯,我們會牢記這次教訓,進一步地對 .NET Core 刨根問底 。