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

相关文章
相关标签/搜索