首页 > 其他分享 >【转载 dudu】 HttpClient使用预热和长连接

【转载 dudu】 HttpClient使用预热和长连接

时间:2024-04-10 09:22:46浏览次数:20  
标签:预热 请求 TCP Elapsed new dudu 连接 HttpClient

C#中HttpClient使用注意:预热与长连接
最近在测试一个第三方API,准备集成在我们的网站应用中。API的调用使用的是.NET中的HttpClient,由于这个API会在关键业务中用到,对调用API的整体响应速度有严格要求,所以对HttpClient有了格外的关注。

开始测试的时候,只在客户端通过HttpClient用PostAsync发了一个http post请求。测试时发现,从创建HttpClient实例,到发出请求,到读取到服务器的响应数据总耗时在2s左右,而且多次测试都是这样。2s的响应速度当然是无法让人接受的,我们希望至少控制在100ms以内。于是开始追查这个问题的原因。

在API的返回数据中包含了该请求在服务端执行的耗时,这个耗时都在20ms以内,问题与服务端API无关。于是把怀疑点放到了网络延迟上,但ping服务器的响应时间都在10ms左右,网络延迟的可能性也不大。

当我们正准备换一个网络环境进行测试时,突然想到,我们的测试方式有些问题。我们只通过HttpClient发了一个PostAsync请求,假如HttpClient在第一次调用时存在某种预热机制(比如在EF中就有这样的机制),现在2s的总耗时可能大多消耗在HttpClient的预热上。

于是修改测试代码,将调用由1次改为100次,然后恍然大悟地发现——只有第1次是2s,接下来的99次都在100ms以内。果然是HttpClient的某种预热机制在搞鬼!

既然知道了是HttpClient预热机制的原因,那我们可以帮HttpClient进行热身,减少第一次请求的耗时。我们尝试了一种预热方式,在正式发http post请求之前,先发一个http head请求,代码如下:

_httpClient.SendAsync(new HttpRequestMessage {
Method = new HttpMethod("HEAD"),
RequestUri = new Uri(BASE_ADDRESS + "/") })
.Result.EnsureSuccessStatusCode();
经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。

在知道第1次HttpClient请求耗时2s的真相之后,我们将目光转向了剩下的99次耗时100ms以内的请求,发现绝大部分请求都在50ms以上。有没有可能将之降至50ms以下?而且,之前一直有这样的纠结:每次调用是不是一定要对HttpClient进行Dispose()?是不是要将HttpClient单例或者静态化(声明为静态变量)?借此机会一起研究一下。

在HttpClient的背后,有一个对请求响应速度有着不容忽视影响的东东——TCP连接。一个HttpClient实例会关联一个TCP连接,在对HttpClient进行Dispose时,会关闭TCP连接(我们用Wireshark进行网络抓包也验证了这一点)。

在之前的测试中,我们每次用HttpClient发请求时,都是新建一个HttpClient实例,用完就对它进行Dispose,代码如下:

using (var httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) })
{
httpClient.PostAsync("/", new FormUrlEncodedContent(parameters));
}
所以每次请求时都要经历新建TCP连接->传数据->关闭连接(也就是通常所说的短连接),而且雪上加霜的是请求用的是https,建立TCP连接时还需要一个基于公私钥加解密的key exchange过程:Client Hello -> Server Hello -> Certificate -> Client Key Exchange -> New Session Ticket。

如果我们想将请求响应时间降至50ms以下,就必须从这个地方下手——重用TCP连接(也就是通常所说的长连接)。要实现长连接,首先需要的就是在HttpClient第1次请求后不关闭TCP连接(不调用Dispose方法);而要让后续的请求继续使用这个未关闭的TCP连接,我们必须要使用同一个HttpClient实例;而要使用同一个HttpClient实例,就得实现HttpClient的单例或者静态化。之前的3 个问题,由于要解决第1个问题,后2个问题变成了别无选择。

为了实现长连接,我们将HttpClient的调用代码改为如下的样子:

复制代码
public class HttpClientTest
{
private static readonly HttpClient _httpClient;

static HttpClientTest()
{
    _httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) };

    //帮HttpClient热身
    _httpClient.SendAsync(new HttpRequestMessage {
            Method = new HttpMethod("HEAD"), 
            RequestUri = new Uri(BASE_ADDRESS + "/") })
        .Result.EnsureSuccessStatusCode();
}

public async Task<string> PostAsync()
{
    var response = await _httpClient.PostAsync("/", new FormUrlEncodedContent(parameters));

    return await response.Content.ReadAsStringAsync();
}

}
复制代码
然后测试一下请求响应时间:

复制代码
Elapsed:750ms
Elapsed:31ms
Elapsed:30ms
Elapsed:43ms
Elapsed:27ms
Elapsed:29ms
Elapsed:28ms
Elapsed:35ms
Elapsed:36ms
Elapsed:31ms
....
复制代码
除了第1次请求,接下来的99次请求绝大多数都在50ms以内。TCP长连接的效果必须的!

通过Wireshak抓包也验证了长连接的效果:

Wireshak抓包

这时,你也许会产生这样的疑问:将HttpClient声明为静态变量,会不会存在线程安全问题?我们当时也有这样的疑问,后来在stackoverflow上找到了答案:

复制代码
As per the comments below (thanks @ischell), the following instance methods are thread safe (all async):
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
复制代码
HttpClient的所有异步方法都是线程安全的,放心使用。

到这里,HttpClient的问题是不是可以完美收官了?。。。稍等,还有一个问题。

客户端虽然保持着TCP连接,但TCP连接是两口子的事,服务器端呢?你不告诉服务器,服务器怎么知道你要一直保持TCP连接呢?对于客户端,保持TCP连接的开销不大;但是对于服务器,则完全不一样的,如果默认都保持TCP连接,那可是要保持成千上万客户端的连接啊。所以,一般的Web服务器都会根据客户端的诉求来决定是否保持TCP连接,这就是keep-alive存在的理由。

所以,我们还要给HttpClient增加一个Connection:keep-alive的请求头,代码如下:

_httpClient.DefaultRequestHeaders.Connection.Add("keep-alive");
现在终于可以收官了。但是肯定不完美,分享的只是解决问题的过程。

标签:预热,请求,TCP,Elapsed,new,dudu,连接,HttpClient
From: https://www.cnblogs.com/superDD/p/18125336

相关文章

  • apache-HttpClient4.5
    packagecom.yonyou.ucf.mdf.sample.utils;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;importcom.fasterxml.jackson.core.type.TypeReference;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;......
  • HttpClient 下载文件
    privatestaticasyncTaskDownloadFile(stringurl,stringfilePath){varprogressMessageHandler=newProgressMessageHandler(newHttpClientHandler());HttpRequestMessagehttpRequestMessage=new(){Method=HttpMethod.Get,Re......
  • 自定义的基于System.Net.Http.HttpClient的WebClient,可以作为微信支付宝的发起请求时
    个人编写的,自己用于自己的微信api的请求的实现当中,源码公开,大家可以查看反编译源码。以下是使用方法:第一步搜索和安装zmjtool第二步发起请求1/**引入命名空间*/2usingZmjTool;34/**发起Get请求*/5using(varcl=newZmjTool.WebClient())6{7cl.......
  • 【Java - 框架 - HttpClient】(01) 使用“HttpClient“爬取网页的代码示例 - 快速上手
    使用"HttpClient"爬取网页的代码示例-快速上手;依赖<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.4.14</version></dependency><dependency>......
  • HttpClient 使用(一)Get、Post 请求
    最近发现不少新人不知道怎么在后台发起Http请求,简单分享一下,HttpClient因为是自带的,所以选择分享这个,还有不少优秀的库,例如Flurl,或者Refit。而说到Get、Post,就不得不提一下rest了,具体是什么呢,感兴趣的可以移步看文档,我不多叙述:传送门按照惯例,废话不多说,代码走起G......
  • 利用Scala与Apache HttpClient实现网络音频流的抓取
    概述在当今数字化时代,网络数据的抓取和处理已成为许多应用程序和服务的重要组成部分。本文将介绍如何利用Scala编程语言结合ApacheHttpClient工具库实现网络音频流的抓取。通过本文,读者将学习如何利用强大的Scala语言和ApacheHttpClient库来抓取网络上的音频数据,以及如......
  • HttpClientHandler VS SocketsHttpHandler
    .NETFramework和.NETCore2.0及更低版本中由HttpClient使用的默认消息处理程序为HttpClientHandler。从.NETCore2.1开始,类SocketsHttpHandler提供了更高级别的HTTP网络类(例如HttpClient)使用的实现。使用SocketsHttpHandler提供了许多优点:与之前的实施相比,性能......
  • C# 采用HttpWebRequest 、WebClient和HttpClient下载https的文件异常问题
    今天有个客户反应,程序下载文件失败,无法正常使用。远程客户电脑后,查看错误日志,都是提示https:****************************.dll等一系列文件的下载错误提示提示基础连接已经关闭:发送时发生错误。在网上找了很多方案都没有能解决,大多都是https链接时增加指定协议,很遗憾未能解......
  • centos服务器中病毒dealer sshc dudu
    参考:http://www.ityunxiu.com/news/industry/26.html1问题这几天服务器一直出问题执行命令:top突然发现系统上多了一个不知道是什么的服务,占用大量的cpu 2查找及解决2.1执行ps-ef发现是这个进程,不知道哪里来的,应该是病毒了 2.2执行命令cronta......
  • 旁门左道:借助 HttpClientHandler 拦截请求,体验 Semantic Kernel 插件
    前天尝试通过one-api+dashscope(阿里云灵积)+qwen(通义千问)运行SemanticKernel插件(Plugin),结果尝试失败,详见前天的博文。今天换一种方式尝试,选择了一个旁门左道走走看,看能不能在不使用大模型的情况下让SemanticKernel插件运行起来,这个旁门左道就是从StephenToub那......