HttpClient
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}"); } } } }
虽然项目已经运行结束,但是连接依然存在,状态为" TIME_WAIT"(继续等待看是否还有延迟的包会传输过来。),240秒(4分钟)后才真正关闭连接。对于高并发的场景,比如每秒 1000 个请求,每个请求都用到 HttpClient ,4分钟内会堆积24万个 tcp 连接,这样的连接爆棚会拖垮服务器。为了避开这个坑,通常采用的变通方法是使用静态的 HttpClient ,但会带来另外一个臭名还没昭著的问题,当 HttpClient 请求的主机名对应的 IP 地址变更时,HttpClient 会蒙在鼓里毫不知情,除非重启应用程序。
默认在windows下,TIME_WAIT状态将会使系统将会保持该连接 240s。
这里也就引出了我上面说的载过的一次坑:在高并发的情况下,连接来不及释放,socket被耗尽,耗尽之后就会出现喜闻乐见的一个错误:
解决办法复用HttpClient
10个变成一个。
好处:
1、可以看到,原先10个连接变成了1个连接。(请不要在意两次示例的目标IP不同---SLB(负载均衡)导致的,都是百度的ip)
2、另外,因为复用了HttpClient,每次RPC请求的时候,实际上还节约了创建通道的时间,在性能压测的时候也是很明显的提升。曾经因为这一举动,将web项目的TPS(系统吞吐量)从单台600瞬间提升到了2000+,页面请求时间也从1-3s减少至100-300ms,甚是让测试组小伙伴膜拜(当然也包括了一些业务代码的细调。),但知道个中缘由后,一个小改动带来的项目性能提升。。。会让人上瘾:)
3、至于如何创建一个静态HttpClient进行复用,大家可以按项目实际来,如直接创建一个“全局”静态对象,或者通过各类DI框架来创建均可。
1、因为是复用的HttpClient,那么一些公共的设置就没办法灵活的调整了,如请求头的自定义。
public static async void HttpMul2Async() { //https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httprequestmessage?redirectedfrom=MSDN&view=netframework-4.7.2 var request = new HttpRequestMessage(HttpMethod.Get, "www.baidu.com"); //request.RequestUri //request.Headers.Accept; //request.Headers. //HttpRequestHeaders hrh = new HttpRequestHeaders(); //request.Method //StreamContent sc = new StreamContent(); MultipartFormDataContent mfdc = new MultipartFormDataContent(); //mfdc.Add // mfdc.Headers //request.Content = await _client.SendAsync(request); for (int i = 0; i < 10; i++) { var result = await _client.GetAsync("http://www.baidu.com"); Console.WriteLine($"{i}:{result.StatusCode}"); } }
HttpClientFactory初识
介绍:
ASP.NET CORE 2.1中新增加的功能。安装包 Microsoft.Extensions.Http
HttpClientFacotry很高效,可以最大程度上节省系统socket。
从微软源码分析,HttpClient继承自HttpMessageInvoker,而HttpMessageInvoker实质就是HttpClientHandler。
HttpClientFactory 创建的HttpClient,也即是HttpClientHandler,只是这些个HttpClient被放到了“池子”中,工厂每次在create的时候会自动判断是新建还是复用。(默认生命周期为2min)
还理解不了的话,可以参考Task和Thread的关系,以前碰到HttpClient这个问题的时候,就一直在想微软什么时候官方出一个HttpClient的Factory,虽然时隔了这么多年直到.NET CORE 2.1才出,但也很是兴奋。
一、ASP.NET CORE MVC
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");
}).SetHandlerLifetime(TimeSpan.FromMinutes(5));
;
services.AddHttpClient();
//other codes
services.AddMvc().AddFluentValidation();
}
}
2、使用
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; } }
二、自定义请求类
1、定义http请求类
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、注入
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); } }
三、自定义请求 接口 实现
1、定义请求接口,请求类
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("/"); } }
设置了BaseAddress ,请求地址的时候,可以直接写想对地址。
2、注入
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); } }
HttpClientFactory进阶
核心功能:
• 管理内部HttpMessageHandler 的生命周期(管理socket链接),灵活应对资源问题和DNS刷新问题
• 支持命名化、类型化配置,集中管理配置,避免冲突
• 灵活的出站请求管道配置,轻松管理请求生命周期
• 内置管道最外层和最内层日志记录器,有Information 和Trace 输出
核心对象:
• HttpClient
• HttpMessageHandler
• SocketsHttpHandler
• DelegatingHandler
• IHttpClientFactory
• IHttpClientBuilde
管道模型:
类似于中间件的设计模式。中间的 DelegatingHandler 就是中间件处理,里面内置中间件LoggingScopeHttp MesageHandler 位于做外层, 记录日志用。最内层的LoggingHttp MessageHandler 记录内层日志。中间是可以自定义的中间件。
SocketsHttpHandler才是真正请求的地方。
用户自定义管道
public class RequestIdDelegatingHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { //处理请求 request.Headers.Add("x-guid", Guid.NewGuid().ToString()); var result = await base.SendAsync(request, cancellationToken); //调用内部handler //处理响应 return result;
public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddControllersAsServices(); services.AddHttpClient(); services.AddScoped<OrderServiceClient>(); services.AddSingleton<RequestIdDelegatingHandler>(); services.AddHttpClient("NamedOrderServiceClient", client => { client.DefaultRequestHeaders.Add("client-name", "namedclient"); client.BaseAddress = new Uri("https://localhost:5003"); }).SetHandlerLifetime(TimeSpan.FromMinutes(20)) .AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>()); services.AddScoped<NamedOrderServiceClient>(); services.AddHttpClient<TypedOrderServiceClient>(client => { client.BaseAddress = new Uri("https://localhost:5003"); }); }
.AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>());
创建HttpClient
1• 工厂模式
public class OrderServiceClient { IHttpClientFactory _httpClientFactory; public OrderServiceClient(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<string> Get() { var client = _httpClientFactory.CreateClient(); //使用client发起HTTP请求 return await client.GetStringAsync("https://localhost:5003/OrderService"); } }
_httpClientFactory.CreateClient();
2• 命名客户端模式
public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddControllersAsServices(); services.AddHttpClient(); services.AddScoped<OrderServiceClient>(); services.AddSingleton<RequestIdDelegatingHandler>(); services.AddHttpClient("NamedOrderServiceClient", client => { client.DefaultRequestHeaders.Add("client-name", "namedclient"); client.BaseAddress = new Uri("https://localhost:5003"); }).SetHandlerLifetime(TimeSpan.FromMinutes(20)) .AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>()); services.AddScoped<NamedOrderServiceClient>(); services.AddHttpClient<TypedOrderServiceClient>(client => { client.BaseAddress = new Uri("https://localhost:5003"); }); }
services.AddHttpClient("NamedOrderServiceClient",
第一个参数是名字,第二个参数是默认的配置。
获取httpclient的地方
_httpClientFactory.CreateClient("名字");
好处:命名客户端,可以为不同的服务配置不同的客户端,不同的客户端有不同而http默认配置,大家的socket都是各自管理。
3• 类型化客户端模式(建议做法)
本质和命名客户端一样,唯一区别以名称作为httpclient的名称。 好处不需要名字 这个字符串去获取。
客户端定义
public class TypedOrderServiceClient { HttpClient _client; public TypedOrderServiceClient(HttpClient client) { _client = client; } public async Task<string> Get() { return await _client.GetStringAsync("/OrderService"); //这里使用相对路径来访问 } }
服务注入
services.AddHttpClient<TypedOrderServiceClient>(client => { client.BaseAddress = new Uri("https://localhost:5003"); });
Grpc
推荐文章:
https://baijiahao.baidu.com/s?id=1633335936037018920&wfr=spider&for=pc