.net core HttpClient 使用之掉坑解析(一)

来源:https://www.cnblogs.com/jlion/archive/2020/05/13/12813692.html
-Advertisement-
Play Games

在我們開發當中經常需要向特定URL地址發送Http請求操作,在.net core 中對httpClient使用不當會造成災難性的問題,這篇文章主要來分享.net core中通過HttpFactory 工廠來使用HttpClient的正確打開方式。 ...


一、前言

在我們開發當中經常需要向特定URL地址發送Http請求操作,在.net core 中對httpClient使用不當會造成災難性的問題,這篇文章主要來分享.net core中通過IHttpClientFactory 工廠來使用HttpClient的正確打開方式。

二、HttpClient使用中的那些坑

2.1 錯誤使用

using(var client = new HttpClient())

我們可以先來做一個簡單的測試,代碼如下:

 public async Task<string> GetBaiduListAsync(string url)
 {
     var html = "";
     for (var i = 0; i < 10; i++)
     {
          using (var client = new System.Net.Http.HttpClient())
          {
             var result=await client.GetStringAsync(url);
             html += result;
           }
     }
     return html;
 }

運行項目輸出結果後,通過netstate查看下TCP連接情況:

雖然項目已經運行結束,但是連接依然存在,狀態為" TIME_WAIT"(繼續等待看是否還有延遲的包會傳輸過來;預設在windows下,TIME_WAIT狀態將會使系統將會保持該連接 240s。
在高併發的情況下,連接來不及釋放,socket被耗盡,耗盡之後就會出現喜聞樂見的一個錯誤:

錯誤原因:

對象所占用資源應該確保及時被釋放掉,但是,對於網路連接而言,這是錯誤的,原因有如下:

  • 網路連接是需要耗費一定時間的,頻繁開啟與關閉連接,性能會受影響;
  • 開啟網路連接時會占用底層socket資源,但在HttpClient調用其本身的Dispose方法時,並不能立刻釋放該資源,這意味著你的程式可能會因為耗盡連接資源而產生災難性的問題。

對於上面的錯誤原因,大家可能會想到使用靜態單例模式的HttpClient,如下:

private static HttpClient Client = new HttpClient();

靜態單例模式雖然可以解決上面問題,但是會帶來另外一個問題:

  • DNS變更會導致不能解析,DNS不會重新載入,需要重啟才能變更(有興趣的大佬可以去嘗試一下)

三、正確使用及源碼分析

HttpClientFactory 以模塊化、可命名、可配置、彈性方式重建了 HttpClient 的使用方式: 由 DI 框架註入 IHttpClientFactory 工廠;由工廠創建 HttpClient 並從內部的 Handler 池分配請求 Handler。

.net core 2.1 開始引入了IHttpClientFactory 工廠類來自動管理IHttpClientFactory 類的創建和資源釋放,可以通過Ioc 註入方式進行使用,代碼如下:

services.AddControllers();
services.AddHttpClient();

調用代碼如下:


private readonly IHttpClientFactory _clientFactory;

public FirstController(IHttpClientFactory clientFactory)
{
    _clientFactory = clientFactory;
}


/// <summary>
/// 
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<string> GetBaiduAsync(string url)
{
    var client = _clientFactory.CreateClient();
    var result = await client.GetStringAsync(url);
    return result;
}

代碼中通過IHttpClientFactory 中的CreateClient()方法進行創建一個HttpClient 對象,但是沒有看到有釋放資源的動作,那它是怎麼釋放的呢?
我們來看看它的主要源代碼

/// <summary>
/// Creates a new <see cref="HttpClient"/> using the default configuration.
/// </summary>
/// <param name="factory">The <see cref="IHttpClientFactory"/>.</param>
/// <returns>An <see cref="HttpClient"/> configured using the default configuration.</returns>
public static HttpClient CreateClient(this IHttpClientFactory factory)
{
     if (factory == null)
     {
        throw new ArgumentNullException(nameof(factory));
     }

     return factory.CreateClient(Options.DefaultName);
}

public HttpClient CreateClient(string name)
{
     if (name == null)
     {
          throw new ArgumentNullException(nameof(name));
     }

     var handler = CreateHandler(name);
     var client = new HttpClient(handler, disposeHandler: false);

     var options = _optionsMonitor.Get(name);
     for (var i = 0; i < options.HttpClientActions.Count; i++)
     {
         options.HttpClientActions[i](client);
     }

     return client;
}

public HttpMessageHandler CreateHandler(string name)
{
     if (name == null)
     {
         throw new ArgumentNullException(nameof(name));
     }

     var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;

     StartHandlerEntryTimer(entry);

     return entry.Handler;
}

代碼中可以看到創建HttpClent 時會先創建HttpMessageHandler對象,而CreateHandler 方法中調用了StartHandlerEntryTimer方法,該方法主要時啟動清理釋放定時器方法,核心代碼如下:

 public DefaultHttpClientFactory(
            IServiceProvider services,
            IServiceScopeFactory scopeFactory,
            ILoggerFactory loggerFactory,
            IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor,
            IEnumerable<IHttpMessageHandlerBuilderFilter> filters)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            if (scopeFactory == null)
            {
                throw new ArgumentNullException(nameof(scopeFactory));
            }

            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            if (optionsMonitor == null)
            {
                throw new ArgumentNullException(nameof(optionsMonitor));
            }

            if (filters == null)
            {
                throw new ArgumentNullException(nameof(filters));
            }

            _services = services;
            _scopeFactory = scopeFactory;
            _optionsMonitor = optionsMonitor;
            _filters = filters.ToArray();

            _logger = loggerFactory.CreateLogger<DefaultHttpClientFactory>();

            // case-sensitive because named options is.
            _activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
            _entryFactory = (name) =>
            {
                return new Lazy<ActiveHandlerTrackingEntry>(() =>
                {
                    return CreateHandlerEntry(name);
                }, LazyThreadSafetyMode.ExecutionAndPublication);
            };

            _expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();
            _expiryCallback = ExpiryTimer_Tick;

            _cleanupTimerLock = new object();
            _cleanupActiveLock = new object();
        }

        // Internal for tests
        internal void ExpiryTimer_Tick(object state)
        {
            var active = (ActiveHandlerTrackingEntry)state;

            // The timer callback should be the only one removing from the active collection. If we can't find
            // our entry in the collection, then this is a bug.
            var removed = _activeHandlers.TryRemove(active.Name, out var found);
            Debug.Assert(removed, "Entry not found. We should always be able to remove the entry");
            Debug.Assert(object.ReferenceEquals(active, found.Value), "Different entry found. The entry should not have been replaced");

            // At this point the handler is no longer 'active' and will not be handed out to any new clients.
            // However we haven't dropped our strong reference to the handler, so we can't yet determine if
            // there are still any other outstanding references (we know there is at least one).
            //
            // We use a different state object to track expired handlers. This allows any other thread that acquired
            // the 'active' entry to use it without safety problems.
            var expired = new ExpiredHandlerTrackingEntry(active);
            _expiredHandlers.Enqueue(expired);

            Log.HandlerExpired(_logger, active.Name, active.Lifetime);

            StartCleanupTimer();
        }

        // Internal so it can be overridden in tests
        internal virtual void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry)
        {
            entry.StartExpiryTimer(_expiryCallback);
        }

從微軟源碼分析,HttpClient繼承自HttpMessageInvoker,而HttpMessageInvoker實質就是HttpClientHandler。

HttpClientFactory 創建的HttpClient,也即是HttpClientHandler,只是這些個HttpClient被放到了“池子”中,工廠每次在create的時候會自動判斷是新建還是復用。(預設生命周期為2min)。
希望這篇文章對你有幫助,如果對你有幫助請點個推薦,感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著信息技術對人們工作生活的影響越來越大,人們對於應用程式的依賴性也越來越大,越來越多的人使用應用程式來解決自己工作和生活中的問題,這也導致應用程式的開發需求越來越大,傳統的應用程式開發方法已經沒有辦法滿足市場的需求,現在很多的人使用低代碼開發平臺來完成應用程式的開發工作,用低代碼開發平臺開發應用程 ...
  • C#實現FTP上傳資料 1.通過用FTP進行上傳文件,首先要實現建立FTP連接,一般建立FTP連接,需要知道FTP配置有關的信息。一般要在Bean中建立一個ServiceFileInfo.cs文件進行記錄,一般需要FTP地址、登錄用戶名和登錄密碼。然後通過其他頁面進行訪問讀取。代碼樣式如下: cla ...
  • 本教程設及到:使用SQL Server查詢分析器創建資料庫;SQL查詢語句常用的一些屬性值;觸發器創建和使用;存儲過程的創建,ASP使用存儲過程。 正文: 一、創建資料庫: 創建一個feedback資料庫,該資料庫的主數據文件的邏輯名稱是feedback,操作系統文件是feedback.mdf Cr ...
  • 1、Startup類里先定義一個全局變數: 2、ConfigureServices方法里寫如下代碼: 3、Configure方法里添加中間件: CORS 中間件必須配置為在對 UseRouting 和 UseEndpoints的調用之間執行。 配置不正確將導致中間件停止正常運行。 原文鏈接:http ...
  • 本文屬於OData系列 目錄(可能會有後續修改) "武裝你的WEBAPI OData入門" "武裝你的WEBAPI OData便捷查詢" 武裝你的WEBAPI OData分頁查詢 武裝你的WEBAPI OData資源更新 武裝你的WEBAPI OData之EDM 武裝你的WEBAPI OData格式 ...
  • 前提說明: 本編文章所屬的微服務框架代碼和對mongo支持的代碼全部放在github,地址:https://github.com/PeyShine/Demo.MicroServer 另外,如果不知道怎麼安裝mongodb可以看這篇:https://www.cnblogs.com/peyshine/p ...
  • 因工作中驗證碼有漏洞,需要進行改造。百度了很多,然後不是這個問題就那個問題, 1,驗證碼圖片生成這裡借鑒了https://blog.csdn.net/z10668107/article/details/103074267這個老哥的。ashx代碼如下 using System; using Syste ...
  • 最近項目需要實現列表排序,由於是winform的,並使用了ListView列表控制項,並且第一列顯示了序號,要求:點擊每列標題實現列表排序,並且序號列要跟隨排序後的數據顯示。 查看了微軟的官方文檔,ListView沒有實現針對某列不參與排序的功能,在DataGridView中就有實現,考慮到更換控制項可 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...