前言

    在 Asp.Net Core 1.0 时代,由于设计上的问题, HttpClient 给开发者带来了无尽的困扰,用 Asp.Net Core 开发团队的话来说就是:我们注意到,HttpClient 被很多开发人员不正确的使用。得益于 .Net Core 不断的版本快速升级;解决方案也一一浮出水面,本文尝试从各个业务场景去剖析 HttpClient 的各种使用方式,从而在开发中正确的使用 HttpClient 进行网络请求。

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.Http

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.HttpClientDemo

Asp.Net Core 轻松学-HttpClient的演进和避坑的更多相关文章

  1. Asp.Net Core 轻松学系列-1阅读指引目录

    https://www.cnblogs.com/viter/p/10474091.html 目录 前言 1. 从安装到配置 2. 业务实现 3. 日志 4. 测试 5. 缓存使用 6.网络和通讯 7. ...

  2. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  3. Asp.Net Core 轻松学-利用文件监视进行快速测试开发

    前言     在进行 Asp.Net Core 应用程序开发过程中,通常的做法是先把业务代码开发完成,然后建立单元测试,最后进入本地系统集成测试:在这个过程中,程序员的大部分时间几乎都花费在开发.运行 ...

  4. Asp.Net Core 轻松学-使用MariaDB/MySql/PostgreSQL和支持多个上下文对象

    前言 在上一篇文章中(Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库)[https://www.cnblogs.com/viter/p/10243577.html],介 ...

  5. Asp.Net Core 轻松学-多线程之Task(补充)

    前言     在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下. 1. 任务的等待 在使用 ...

  6. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  7. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  8. Asp.Net Core 轻松学-一行代码搞定文件上传 JSONHelper

    Asp.Net Core 轻松学-一行代码搞定文件上传   前言     在 Web 应用程序开发过程中,总是无法避免涉及到文件上传,这次我们来聊一聊怎么去实现一个简单方便可复用文件上传功能:通过创建 ...

  9. Asp.Net Core 轻松学-多线程之取消令牌

    前言     取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码.提升服务性能的效果 ...

随机推荐

  1. UOJ182 a^-1 + b problem 解题报告

    题目描述 有一个长度为\(n(n\le 10^5)\)的数列,在模\(M\)意义下进行\(m(m \le50000)\)次操作,每次操作形如以下两种形式: 1 \(x\) 表示每个数加\(x(0 \l ...

  2. bzoj5250 [2018多省省队联测]秘密袭击

    博主蒟蒻,目前还不会动态dp,所以下面说的是一个并不优秀的暴力,我会补的! 我们考虑按权值从大到小依次点亮每个点,相同权值可以同时点亮,每次点亮后,我们进行一次树形背包. 处理出$f[i][j]$表示 ...

  3. 【最小生成树】BZOJ1016: [JSOI2008]最小生成树计数

    Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的 ...

  4. Error【0002】:YUM本地源配置问题

    1.1 问题背景 通过VMware workstation创建虚拟机,在虚拟机的CDROM设备中,装载操作系统镜像.然后通过mount -o loop的方式,将CDROM设备挂载到系统的/mnt/cd ...

  5. MYSQL—— char 与 varchar的区别!

    一.char 和 varchar 的区别: 1)取值范围: char:取值范围:0~255 varchar:取值范围:0~65535 2)空间占用与速度: char: 定长字符串,占用空间大,速度快, ...

  6. 浅谈.Net异步编程的前世今生----APM篇

    前言 在.Net程序开发过程中,我们经常会遇到如下场景: 编写WinForm程序客户端,需要查询数据库获取数据,于是我们根据需求写好了代码后,点击查询,发现界面卡死,无法响应.经过调试,发现查询数据库 ...

  7. 客户端热更新框架之UI热更框架设计(上)

    什么是热更新,为什么需要热更新?          热更新是目前各大手游等众多App常用的更新方式.简单来说就是在用户通过App Store下载App之后,打开App时遇到的即时更新.对于手游客户端来 ...

  8. 【转】mip-semi-fixed 走走又停停

    写在前面 MIP 中悬浮元素的特殊情况 其实组件上线已经有一段时间了,最开始看到这个需求是站长提交了一个这中功能的组件过来,不过看过代码立刻就想到了 MIP 页面的特殊性:从结果页打开的 MIP 页面 ...

  9. JavaWeb 乱码问题终极解决方案!

    经常有读者在公众号上问 JavaWeb 乱码的问题,昨天又有一个小伙伴问及此事,其实这个问题很简单,但是想要说清楚却并不容易,因为每个人乱码的原因都不一样,给每位小伙伴都把乱码的原因讲一遍也挺费时间的 ...

  10. RabbitMQ 消息队列 入门 第一章

    RabbitMQ : 官网:https://www.rabbitmq.com/ GitHub:https://github.com/rabbitmq?q=rabbitmq 第一步安装: 点击  htt ...