Asp.Net Core 輕鬆學-HttpClient的演進和避坑

前言

    在 Asp.Net Core 1.0 時代,因爲設計上的問題, HttpClient 給開發者帶來了無盡的困擾,用 Asp.Net Core 開發團隊的話來講就是:咱們注意到,HttpClient 被不少開發人員不正確的使用。得益於 .Net Core 不斷的版本快速升級;解決方案也一一浮出水面,本文嘗試從各個業務場景去剖析 HttpClient 的各類使用方式,從而在開發中正確的使用 HttpClient 進行網絡請求。html

1.0時代發生的事情

1.1 在 1.0 時代,部署在 Linux 上的 Asp.Net Core 應用程序進程出現 「套接字資源耗盡」 的異常,該異常一般是因爲不停的建立 HttpClient 的實例後產生,由於每建立一個新的 HttpClient 對象,將會消耗一個套接字資源,而在對象使用完成後,因爲設計上的問題,即便手動釋放 HttpClient,也極有可能沒法釋放套接字資源。
1.2 思考下面的代碼,在遠古時代,下面的代碼將會形成 「套接字資源耗盡」 的異常
public HttpClient CreateHttpClient()
        {
            return new HttpClient();
        }

        // 或者
        public async Task<string> GetData()
        {
            using (var client = new HttpClient())
            {
                var data = await client.GetAsync("https://www.cnblogs.com");
            }

            return null;
        }
1.3 繼而引出了下面的使用方法,利用靜態對象進行網絡請求
private static HttpClient httpClient = null;
        public HttpClient CreateHttpClient()
        {
            if (httpClient == null)
                httpClient = new HttpClient();

            return httpClient;
        }
1.4 上面使用靜態對象的方式能夠避免 「套接字資源耗盡」 的異常,可是,有一個致命的問題,當主機 DNS 更新時,你可能會收到另一個異常
An error occurred while sending the request. Couldn't resolve host name An error occurred while sending the request. Couldn't resolve host name
1.5 該異常指示沒法解析主機名稱,其實就是由於靜態 HttpClient 對象不會隨着主機 DNS 更新而更新,這個時候,你一般須要作的就是:重啓服務!

2. 正確的使用 HttpClient

2.1 時間來到了 .Net Core 的 2.2 時代(其實2.1就能夠),官方推薦咱們應該使用依賴注入的方式去使用 HttpClient,好比在 Startup.cs 的 ConfigureServices 方法中加入下面的代碼
public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient();
        }
2.2 而後再控制器中經過構造方法注入 HttpClient 對象進行使用
public class ValuesController : ControllerBase
    {
        private HttpClient httpClient;
        public ValuesController(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        ...
    }
2.3 在新版本的 Asp.Net Core 中,Asp.Net Core 開發團隊引入了 HttpClientFactory
public HttpClient CreateHttpClient()
        {
            return HttpClientFactory.Create();
        }
2.4 HttpClientFactory 的主要工做就是建立 HttpClient 對象,可是在建立過程當中,經過爲每一個 HttpClient 對象建立一個單獨的清理句柄來對 HttpClient 進行跟蹤和管理,以確保在對象使用完成後可以及時的釋放網絡請求的資源,也就是套接字,具體 HttpClientFactory 內部原理可參考 李志章-DotNetCore深刻了解之三HttpClientFactory類.
2.5 更重要的是,HttpClientFactory 內部管理着一個鏈接句柄池,一旦高併發的到來,HttpClientFactory 內句柄池內使用完成可是未被釋放的句柄將被從新使用,雖然使用 HttpClientFactory.Create() 每次都是返回一個新的 HttpClient 對象,可是其背後的管理句柄是能夠複用的,換句話說就是 「套接字複用」,並且還不會有 DNS 沒法同步更新的問題
2.6 因此如今咱們明白了爲何要使用 HttpClientFactory 來建立 HttpClient 對象

3. 使用類型化的 HttpClient 客戶端

3.1 在常規應用和微服務的應用場景中,均可以使用類型化的客戶端,類型化客戶端這個詞若是不太好理解,那麼你能夠理解爲爲每一個業務單獨的使用一個 HttpClient 客戶端,好比獲取天氣預報,思考下面的代碼
public class WeatherService
{
    private HttpClient httpClient;
    public WeatherService(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        this.httpClient.BaseAddress = new Uri("http://www.weather.com.cn");
        this.httpClient.Timeout = TimeSpan.FromSeconds(30);
    }

    public async Task<string> GetData()
    {
        var data = await this.httpClient.GetAsync("/data/sk/101010100.html");
        var result = await data.Content.ReadAsStringAsync();

        return result;
    }
}
3.2 爲了在控制器中更好的使用 WeatherService,咱們須要把 WeatherService 注入到服務中
public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddHttpClient();
    }

    // 而後,在控制器中使用以下代碼

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private WeatherService weatherService;
        public ValuesController(WeatherService weatherService)
        {
            this.weatherService = weatherService;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            string result = string.Empty;
            try
            {
                result = await weatherService.GetData();
            }
            catch { }

            return new JsonResult(new { result });
        }
    }
3.3 運行程序,你將獲得 北京市 的天氣
{
result: "{"weatherinfo":{"city":"北京","cityid":"101010100","temp":"27.9","WD":"南風","WS":"小於3級","SD":"28%","AP":"1002hPa","njd":"暫無實況","WSE":"<3","time":"17:55","sm":"2.1","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}"
}
3.4 在微服務中,這種作法很常見,並且很是有用,經過使用類型化的客戶端,除了在構造方法中注入 HttpClient 外,咱們還能夠注入任何須要的東西到 WeatherService 中,更重要的是,能夠對業務應用擴展策略,還方便了管理
3.5 在 WeatherService 類型化客戶端中,雖然每次都是建立了一個新的 HttpClient 對象,可是其內部的句柄和其它 HttpClient 是共用同一個句柄池,無需擔憂

4.對 HttpClient 應用策略

4.1 下面說到的策略組件是業內大名鼎鼎的 Polly (波莉),GitHub 地址:https://github.com/App-vNext/Polly
4.2 使用重試策略,參考 Polly 的 Wiki 示例代碼,使用起來很是簡單

首先須要從 NuGet 中引用包
Polly
Polly.Extensions.Httplinux

4.3 接着在 Startup.cs ConfigureServices 方法中應用策略
public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient<WeatherService>()
                    .SetHandlerLifetime(TimeSpan.FromMinutes(5))
                    .AddPolicyHandler(policy =>
                    {
                        return HttpPolicyExtensions.HandleTransientHttpError()
                                                   .WaitAndRetryAsync(3,
                                                   retryAttempt => TimeSpan.FromSeconds(2),
                                                   (exception, timeSpan, retryCount, context) =>
                                                   {
                                                       Console.ForegroundColor = ConsoleColor.Yellow;
                                                       Console.WriteLine("請求出錯了:{0} | {1} ", timeSpan, retryCount);
                                                       Console.ForegroundColor = ConsoleColor.Gray;
                                                   });
                    });
        }
4.4 以上代碼表示在請求發生錯誤的狀況下,重試 3 次,每次 2 秒,針對高併發的請求,重試請求間隔建議使用隨機值

結語

  • 本章着重介紹了 HttpClient 在 Asp.Net Core 中的前世此生,簡單介紹了使用原理,介紹了各類使用 HttpClient 的方式
  • 介紹了使用了 Polly 對在類型化的客戶端上使用 HttpClient 重試策略,由於對 Polly 理解不夠,其它的策略就再也不介紹,你們能夠到 Polly 的 Wiki 上深刻了解
  • 最後經過一個簡單的獲取天氣預報的小實例來演示類型化的客戶端使用場景

示例代碼下載

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.HttpClientDemogit

相關文章
相關標籤/搜索