文章目錄
HttpClient 無法反應 DNS 異動的解決方式
之前筆記 探討 HttpClient 可能的問題 提到使用 HttpCLient 時避免 socket 耗盡的方式就是只建立一個 HttpClient instance (透過 static or singleton),但這樣的方式卻會造成 DNS 紀錄出現變動被忽略進而影響系統正確運行
筆記中有提到可以透過將 HttpClient 的 DefaultRequestHeaders.ConnectionClose
屬性設定為 true
,也就是將 HTTP 的 keep-alive header 設為 false
,讓 socket 在每次處理完 request 即關閉,這幾天查資料時發現還有其他做法可以使用,一併紀錄一下
前提設定
使用 staticHttpClient instance 來取得
/
資料public class StaticHttpClientService { private static readonly HttpClient _httpClient; static StaticHttpClientService() { var baseUri = new Uri("http://blog.yowko.com"); _httpClient = new HttpClient(); _httpClient.BaseAddress = baseUri; } public HttpClient HttpclientInstance = _httpClient; }
透過修改 hosts 檔案來模擬 DNS 修改
修改方式請參考 在 Windows 環境將特定網址指向不同 IP
使用環境
- Visual Studio 2017 15.9.4
- .NET Framework 4.7.2
BenchmarkDotNet 0.11.3
為避免外部網路干擾,透過修改 hosts file 將
blog.yowko.com
指向 127.0.0.1
解決方式
dispose HttpClient
每次 request 結束即 dispose 的做法存在與 TCP 協定及 OS 層實作的效能問題,應重複使用 HttpClient 並自行管理 dispose 時間
public class StaticHttpClientService { private static HttpClient _httpClient; private static DateTime _TTL; private static void createInstance() { _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri("http://blog.yowko.com"); //設定 dispose HttpClient 的時間 _TTL = DateTime.UtcNow.AddMinutes(1); } static StaticHttpClientService() { createInstance(); } public HttpClient HttpclientInstance { get { if (DateTime.UtcNow > _TTL) { _httpClient.Dispose(); //重新建立 HttpClient createInstance(); } return _httpClient; } } }
關閉 socket 連線
將 HttpClient 的
DefaultRequestHeaders.ConnectionClose
屬性設定為true
,也就是將 HTTP 的 keep-alive header 設為false
,讓 socket 在每次處理完 request 即關閉public class StaticHttpClientService { private static readonly HttpClient _httpClient; static StaticHttpClientService() { var baseUri = new Uri("http://blog.yowko.com"); _httpClient = new HttpClient(); _httpClient.BaseAddress = baseUri; _httpClient.DefaultRequestHeaders.ConnectionClose = true; } public HttpClient HttpclientInstance = _httpClient; }
設定釋放 socket 連線時間
避免每次皆關閉 socket 而造成無謂的效能損耗
- 修改
ConnectionLeaseTimeout
時間 : 用來管理 TCP socket 保持開啟的時間,預設為-1
永遠開啟 - 修改
DnsRefreshTimeout
時間: 用來管理 DNS 更新間隔,預設為12000
(兩分鐘)
public class StaticHttpClientService { private static readonly HttpClient _httpClient; static StaticHttpClientService() { var baseUri = new Uri("http://blog.yowko.com"); _httpClient = new HttpClient(); _httpClient.BaseAddress = baseUri; //設定 1 分鐘沒有活動即關閉連線,預設 -1 (永不關閉) ServicePointManager.FindServicePoint(baseUri) .ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; //設定 1 分鐘更新 DNS,預設 12000 (2 分鐘) ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; ; } public HttpClient HttpclientInstance = _httpClient; }
- 修改
效能比較
第一次
Method Mean Error StdDev Dispose 407.8 us 7.849 us 8.060 us ConnectionClose 691.4 us 15.557 us 45.869 us ConnectionLeaseTimeout 423.3 us 8.316 us 16.609 us 第二次
Method Mean Error StdDev Dispose 410.3 us 6.086 us 5.395 us ConnectionClose 732.0 us 14.589 us 21.384 us ConnectionLeaseTimeout 415.8 us 5.225 us 4.887 us 第三次
Method Mean Error StdDev Dispose 416.6 us 2.951 us 2.760 us ConnectionClose 759.2 us 7.041 us 6.586 us ConnectionLeaseTimeout 422.3 us 11.351 us 14.355 us
心得
以效能數據來看,雖說三者執行時間都非常快,但每次 request 都關閉 socket 執行時間是另外兩者的 1.8 倍以上,我相信這在高流量環境下是不被允許的,差距太大了
至於自行管理 HttpClient instance 及使用 ServicePointManager 兩者差距就微乎其微了
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-02
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。