httpclient 设置短连接_HttpClientFactory使用说明及HttpClient的回顾对比

(给DotNet加星标,提升.Net技能)

转自:Coder.X cnblogs.com/deepthought/p/11303015.html

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 连接情况:

51066ce731eda77e215804ba9a8be1a2.png

虽然项目已经运行结束,但是连接依然存在,状态为 "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}");
}
}
}

975152de80dfa2fd7b4d5fe5eb6955c9.png

可以看到,原先 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 TaskTest(){
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 TaskGet(){
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 TaskGet(){
string result = await _sampleClient.GetData();
return Ok(result);
}
}

推荐阅读

(点击标题可跳转阅读)

ASP.NET Core中HttpClient的使用方式

.NET Core中正确使用HttpClient的姿势

HttpClient在.NET Core中的正确打开方式

看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

934f60e5dda4cf291387c5dd66973c52.png

好文章,我在看❤️

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值