目录
HttpClient 日常使用及坑点:
HttpClientFactory 优势:
HttpClientFactory 使用方法:
实战用法 1: 常规用法
1. 在 Startup.cs 中进行注册
2. 使用, 这里直接以 controller 为例, 其他地方自行 DI
实战用法 2: 使用自定义类执行 HttpClientFactory 请求
1. 自定义 HttpClientFactory 请求类
2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient, 代码如下,
3. 调用:
实战用法 3: 完全封装 HttpClient 可以使用下面方法
1. 自定义 HttpClientFactory 请求类
2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient, 代码如下,
3. 调用:
HttpClient 日常使用及坑点:
在 C# 中, 平时我们在使用 HttpClient 的时候, 会将 HttpClient 包裹在 using 内部进行声明和初始化, 如:
- using(var httpClient = new HttpClient())
- {
- //other codes
- }
至于为什么? 无外乎是: 项目代码中就是这样写的, 依葫芦画瓢 / 别人就是这样用的 / 在微软官方的 ASP.NET 教程中也是这么干的.
说的技术范点: 当你使用继承了 IDisposable 接口的对象时, 建议在 using 代码块中声明和初始化, 当 using 代码段执行完成后, 会自动释放该对象而不需要手动进行显示 Dispose 操作.
但这里, HttpClient 这个对象有点特殊, 虽然继承了 IDisposable 接口, 但它是可以被共享的 (或者说可以被复用), 且线程安全. 从项目经验来看, 倒是建议在整个应用的生命周期内, 复用 HttpClient 实例, 而不是每次 RPC 请求的时候就实例化一个.(之前在优化公司一个 web 项目的时候, 也曾经因为 HttpClient 载过一次坑, 后面我会进行简述.)
我们先来用个简单的例子做下测试, 看为什么不要每次 RPC 请求都实例化一个 HttpClient:
- public class Program
- {
- static void Main(string[] args)
- {
- HttpAsync();
- Console.WriteLine("Hello World!");
- Console.Read();
- }
- public static async void HttpAsync()
- {
- for (int i = 0; i <10; i++)
- {
- using (var client = new HttpClient())
- {
- var result = await client.GetAsync("http://www.baidu.com");
- Console.WriteLine($"{i}:{result.StatusCode}");
- }
- }
- }
- }
运行项目输出结果后, 通过 netstate 查看下 TCP 连接情况:
虽然项目已经运行结束, 但是连接依然存在, 状态为 "TIME_WAIT"(继续等待看是否还有延迟的包会传输过来.).
默认在 Windows 下, TIME_WAIT 状态将会使系统将会保持该连接 240s.
这里也就引出了我上面说的载过的一次坑: 在高并发的情况下, 连接来不及释放, socket 被耗尽, 耗尽之后就会出现喜闻乐见的一个错误:
- # 使用 jemter 压测复现错误信息:
- Unable to connect to the remote serverSystem.NET.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.
说白话: 就是会出现 "各种套接字问题".(码 WCF 的童鞋可能更加记忆尤新, 问题追根溯源都是换汤不换药.)
熊厂里面能够搜索出来的解决方法, 基本都是 "减少超时时间", 但人为减少了超时时间会出现各种莫名其妙的错误. 且无法避免服务器迟早崩溃的问题.
可以通过注册表进行修改默认值:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])
那么如何处理这个问题? 答案已经在上面说了,"复用 HttpClient" 即可. 如:
- public class Program
- {
- private static readonly HttpClient _client = new HttpClient();
- static void Main(string[] args)
- {
- HttpAsync();
- Console.WriteLine("Hello World!");
- Console.Read();
- }
- public static async void HttpAsync()
- {
- for (int i = 0; i < 10; i++)
- {
- var result = await _client.GetAsync("http://www.baidu.com");
- Console.WriteLine($"{i}:{result.StatusCode}");
- }
- }
- }
可以看到, 原先 10 个连接变成了 1 个连接.(请不要在意两次示例的目标 IP 不同 ---SLB 导致的, 都是百度的 ip)
另外, 因为复用了 HttpClient, 每次 RPC 请求的时候, 实际上还节约了创建通道的时间, 在性能压测的时候也是很明显的提升. 曾经因为这一举动, 将 Web 项目的 TPS 从单台 600 瞬间提升到了 2000+, 页面请求时间也从 1-3s 减少至 100-300ms, 甚是让测试组小伙伴膜拜 (当然也包括了一些业务代码的细调.), 但知道个中缘由后, 一个小改动带来的项目性能提升... 会让人上瘾:)
至于如何创建一个静态 HttpClient 进行复用, 大家可以按项目实际来, 如直接创建一个 "全局" 静态对象, 或者通过各类 DI 框架来创建均可.
但这么调整 HttpClient 的引用后, 依然存在一些问题可能会影响到你的项目 (尚未影响到我: P), 如:
因为是复用的 HttpClient, 那么一些公共的设置就没办法灵活的调整了, 如请求头的自定义.
因为 HttpClient 请求每个 url 时, 会缓存该 url 对应的主机 ip, 从而会导致 DNS 更新失效 (TTL 失效了)
那么有没有办法解决 HttpClient 的这些个问题? 直到我遇到了 HttpClientFactory, 瞬间写代码幸福感倍升, 也感慨新时代的童鞋们真的太幸福了, 老一辈踩的坑可以 "完美" 规避掉了.
HttpClientFactory 优势:
HttpClientFactory 是 ASP.NET CORE 2.1 中新增加的功能.
"完美" 解决了我多年来遇到的这些坑, 可以更加专注于业务代码.
HttpClientFacotry 很高效, 可以最大程度上节省系统 socket.("JUST USE IT AND FXXK SHUT UP":P)
Factory, 顾名思义 HttpClientFactory 就是 HttpClient 的工厂, 内部已经帮我们处理好了对 HttpClient 的管理, 不需要我们人工进行对象释放, 同时, 支持自定义请求头, 支持 DNS 更新等等等.
从微软源码分析, HttpClient 继承自 HttpMessageInvoker, 而 HttpMessageInvoker 实质就是 HttpClientHandler.
HttpClientFactory 创建的 HttpClient, 也即是 HttpClientHandler, 只是这些个 HttpClient 被放到了 "池子" 中, 工厂每次在 create 的时候会自动判断是新建还是复用.(默认生命周期为 2min)
还理解不了的话, 可以参考 Task 和 Thread 的关系, 以前碰到 HttpClient 这个问题的时候, 就一直在想微软什么时候官方出一个 HttpClient 的 Factory, 虽然时隔了这么多年直到 .NET CORE 2.1 才出, 但也很是兴奋.
HttpClientFactory 使用方法:
借助 ASP.NET CORE MVC, 可以很方便的进行 HttpClient 的使用
实战用法 1: 常规用法
统一在发布项目中声明和配置.
1. 在 Startup.cs 中进行注册
- public class Startup
- {
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
- public IConfiguration Configuration { get; }
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- //other codes
- services.AddHttpClient("client_1",config=> // 这里指定的 name=client_1, 可以方便我们后期服用该实例
- {
- config.BaseAddress= new Uri("http://client_1.com");
- config.DefaultRequestHeaders.Add("header_1","header_1");
- });
- services.AddHttpClient("client_2",config=>
- {
- config.BaseAddress= new Uri("http://client_2.com");
- config.DefaultRequestHeaders.Add("header_2","header_2");
- });
- services.AddHttpClient();
- //other codes
- services.AddMvc().AddFluentValidation();
- }
- }
2. 使用, 这里直接以 controller 为例, 其他地方自行 DI
- public class TestController : ControllerBase
- {
- private readonly IHttpClientFactory _httpClient;
- public TestController(IHttpClientFactory httpClient)
- {
- _httpClient = httpClient;
- }
- public async Task<ActionResult> Test()
- {
- var client = _httpClient.CreateClient("client_1"); // 复用在 Startup 中定义的 client_1 的 httpclient
- var result = await client.GetStringAsync("/page1.html");
- var client2 = _httpClient.CreateClient(); // 新建一个 HttpClient
- var result2 = await client.GetStringAsync("http://www.site.com/XXX.html");
- return null;
- }
- }
实战用法 2: 使用自定义类执行 HttpClientFactory 请求
1. 自定义 HttpClientFactory 请求类
- public class SampleClient
- {
- public HttpClient Client { get; private set; }
- public SampleClient(HttpClient httpClient)
- {
- httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
- httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
- httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- Client = httpClient;
- }
- }
2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient, 代码如下,
services.AddHttpClient<SampleClient>();
3. 调用:
- public class ValuesController : Controller
- {
- private readonly SampleClient _sampleClient;;
- public ValuesController(SampleClient sampleClient)
- {
- _sampleClient = sampleClient;
- }
- [HttpGet]
- public async Task<ActionResult> Get()
- {
- string result = await _sampleClient.client.GetStringAsync("/");
- return Ok(result);
- }
- }
实战用法 3: 完全封装 HttpClient 可以使用下面方法
1. 自定义 HttpClientFactory 请求类
- public interface ISampleClient
- {
- Task<string> GetData();
- }
- public class SampleClient : ISampleClient
- {
- private readonly HttpClient _client;
- public SampleClient(HttpClient httpClient)
- {
- httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
- httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
- httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- _client = httpClient;
- }
- public async Task<string> GetData()
- {
- return await _client.GetStringAsync("/");
- }
- }
2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient, 代码如下,
services.AddHttpClient<ISampleClient, SampleClient>();
3. 调用:
- public class ValuesController : Controller
- {
- private readonly ISampleClient _sampleClient;;
- public ValuesController(ISampleClient sampleClient)
- {
- _sampleClient = sampleClient;
- }
- [HttpGet]
- public async Task<ActionResult> Get()
- {
- string result = await _sampleClient.GetData();
- return Ok(result);
- }
- }
来源: https://www.cnblogs.com/deepthought/p/11303015.html