.NET Core HttpClient请求异常详细情况分析
前言
最近项目上每天间断性捕获到HttpClient请求异常,感觉有点奇怪,于是乎观察了两三天,通过日志以及对接方沟通确认等等,查看对应版本源码,尝试添加部分配置发布后,观察十几小时暂无异常情况出现,貌似问题已得到解决,若有后续继续更新。HttpClient来源:netstandard2.0
异常问题
场景:将相关厂家地磁设备(停车进出场)推送数据,转发至对接方。最近一个星期经过观察会出现两种异常情况,一种是请求连接操作被取消,另外一种则是请求处理过程中操作被取消,具体异常信息请见如下图


我们知道HttpClient默认超时时间为100s,但项目默认设置请求超时时间为30s,初次分析异常情况来看,请求超时导致请求连接被取消异常,首先我telnet对接方端口通畅,于是乎与对接方交涉,是否存在从请求到对接方接口有额外前置处理,以及网络是否存在波动等等,排查得知相关猜测都予以否决,网络无任何问题
问题排查分析
既然网络没有任何问题,难道是对接方即服务端处理数据量巨大,导致请求应答超时?于是乎对接方甩出几张最近消息接收数据


从上述两张图来看,最多的一天也才90来万,最近几天请求失败的数据大概2百来条,从我们平台打印日志来看,每秒请求大致是10个左右,通过HTTP对接完全可以承载。然后,因为我们将数据(JSON,数据大小几乎可以忽略不计)转发到对接方,对接方拿到数据后不会进行任何额外处理,直接存储,所以我们请求超时时间30s,怎么会导致超时而引发异常呢?很奇怪,没辙了,只能拿起终极武器,tcp抓包分析,重新学习了tcp/ip协议族一波
WireShark抓包分析
为打开分析上述pcap文件,提前安装抓包软件WireShark最新版本,由于软件项目部署在Linux上,我们通过如下命令进行抓包
tcpdump -i any port 1443 -w exception.pcap

从如下抓包信息可以直接知道,对接方使用了HTTPS协议,具体请看如下图

默认打开如上图所示,其中time为时间戳,为找到我们指定时间点(2021-06-04 15:46:17.296),通过tab:视图-时间显示格式-日期和时间

接下来我们找到包文件中导致请求被取消异常的具体时间节点(2021-06-04 15:46:17.296)

在TCP协议中RST表示复位,用于异常时关闭连接。在发送RST包关闭连接时,不必等待缓冲区的包都发出去,直接丢弃缓冲区的包而发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。好像有点眉头了,继续往下看

好家伙,结合这张图来看,基本上可以得出结论:原来是我们平台主动重置了连接,紧接着又开始了多次进行三次握手连接即(SYN、SYN/ACK、ACK)
示例代码分析
推送逻辑是在类库中使用HttpClient,所以没有使用HttpClientFactory,因此定义静态变量来使用HttpClient,而非每一个请求就实例化一个HttpClient,
接下来我们来详细分析项目示例代码并对其进行改进
static class Program
{
static HttpClient httpClient = CreateHttpClient();
static Program()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, error) => true,
} static async Task Main(string[] args)
{
await httpClient.PostAsync("", new StringContent(""));
} static HttpClient CreateHttpClient()
{
var client = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true
})
{
Timeout = TimeSpan.FromSeconds(30)
}; client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("ContentType", "application/json");
return client;
}
}
若对接方仅使用HTTPS协议,无需验证证书,最好是忽略证书验证,否则有可能会引起建立验证证书连接异常,即添加
ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true
我们观察上述代码,有两个地方都对证书验证进行了设置,一个是在静态构造函数中ServicePointManager(简称SP),另外则在实例化HttpClient构造函数中即HttpClientHandler(简称HCH),那么这二者是否有使用上的限制呢?
在.NET Framework中,内置的HttpClient建立在HttpWebRequest之上,因此可以使用SC来配置
在.NET Core中,通过SP配置证书信息仅影响HttpWebRequest,而对HttpClient无效,需通过HCH配置来达到相同目的
所以去除在静态构造函数中对忽略证书的配置,改为在HttpClientHandler中
var client = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true,
SslProtocols = SslProtocols.Tls12
})
回到本文的话题,为什么会重置连接即主动关闭连接呢?我们已分析过,和上述配置30s超时没有关系,主要有两方面原因
翻开并温习《图解HTTP》一书,如果请求频繁,最好建立持久连接,减少TCP连接的重复建立和断开所造成的额外开销,从而减轻服务端负载即重用HTTP连接,也称为Http keep-alive,持久连接的特点是,只要任意一方没有明确提出断开连接,否则保持TCP连接状态
配置keep-alive我们俗称为保活机制,所以在默认请求头中添加如下一行
//增加保活机制,表明连接为长连接
client.DefaultRequestHeaders.Connection.Add("keep-alive");
上述只是在报文头中添加持久化连接标识,但不意味着就一定生效,因为默认是禁用持久化连接,所以为了保险起见,添加如下代码
//启用保活机制(保持活动超时设置为 2 小时,并将保持活动间隔设置为 1 秒。)
ServicePointManager.SetTcpKeepAlive(true, 7200000, 1000);
有个让我很疑惑的问题,通过查看设置启用持久化连接源码得知,这样设置意义在哪里?没弄明白,源码如下
public static void SetTcpKeepAlive(bool enabled, int keepAliveTime, int keepAliveInterval)
{
if (enabled)
{
if (keepAliveTime <= 0)
{
throw new ArgumentOutOfRangeException(nameof(keepAliveTime));
}
if (keepAliveInterval <= 0)
{
throw new ArgumentOutOfRangeException(nameof(keepAliveInterval));
}
}
}
最关键的一点则是默认持久化连接数为2,非持久化连接为4
public class ServicePointManager
{
public const int DefaultNonPersistentConnectionLimit = 4;
public const int DefaultPersistentConnectionLimit = 2; private ServicePointManager() { }
}
那么问题是否就已很明了,项目中使用非持久化连接,即连接为4,未深究源码具体细节,大胆猜想一下,若连接大于4,是否会出现将此前连接主动关闭,重建新的连接请求呢?最终我们讲原始代码修改为如下形式
static class Program
{
static HttpClient httpClient = CreateHttpClient(); static Program()
{
//默认连接数限制为2,增加连接数限制
ServicePointManager.DefaultConnectionLimit = 512; //启用保活机制(保持活动超时设置为 2 小时,并将保持活动间隔设置为 1 秒。)
ServicePointManager.SetTcpKeepAlive(true, 7200000, 1000);
} static async Task Main(string[] args)
{
await httpClient.PostAsync("", new StringContent("")); Console.WriteLine("Hello World!");
} static HttpClient CreateHttpClient()
{
var client = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true,
SslProtocols = SslProtocols.Tls12
})
{
Timeout = TimeSpan.FromSeconds(30)
}; client.DefaultRequestHeaders.Accept.Clear();
//增加保活机制,表明连接为长连接
client.DefaultRequestHeaders.Connection.Add("keep-alive");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("ContentType", "application/json");
return client;
}
}
进行如上设置后,通过一天观察,再未出现相关异常,至此问题解决告一段落,希望没有后续......
总结
强烈建议:利用HttpClient发送请求设置持久化连接和根据实际业务评估增加连接数,而非默认的持久化连接为2,非持久化连接为4,否则极易出现相关异常。
若非常清楚默认连接数限制,可能并算不上什么问题,也不存在如此诸多分析,不过对于我而言,收获的是在问题排查过程中,对可能干扰信息的过滤、筛选、确认以及对网络协议进一步的加深。
引发思考:利用HttpClientFactory创建HttpClient是否有默认连接限制,据我所知,好像可以通过属性MaxConnectionsPerServer来配置
在.NET Core和.NET Framework中相关配置还是有些变化,最好是根据对应版本在官网上确认下
.NET Core HttpClient请求异常详细情况分析的更多相关文章
- Web Api 中Get 和 Post 请求的多种情况分析
转自:http://www.cnblogs.com/babycool/p/3922738.html 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用J ...
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...
- [1.6W字] 浏览器跨域请求限制的详细原理分析&寻找一种最简单的方式实现XHR跨域(9种方法, 附大招可以纯前端实现跨域!)
Title/ 浏览器跨域(CrossOrigin)请求的原理, 以及解决方案详细指南 #flight.Archives011 序: 最近看到又有一波新的创作活动了, 官方给出的话题中有一个" ...
- 【ASP.NET Core】处理异常--转
老周写的[ASP.NET Core]处理异常非常的通俗易懂,拿来记录下. 转自老周:http://www.cnblogs.com/tcjiaan/p/8461408.html 今天咱们聊聊有关异常处理 ...
- .Net Core HttpClient处理响应压缩
前言 在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采 ...
- IIS提示“异常详细信息: System.Runtime.InteropServices.ExternalException: 无法执行程序”
先来看错误提示: 无法执行程序.所执行的命令为 "C:/Windows/Microsoft.NET/Framework/v3.5/csc.exe" /noconfig /fullp ...
- 在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程)
在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程) 原文链接:http://www.360doc.com/content/14/1117/10/16948208_42571794 ...
- MongoDB数据库索引构建情况分析
前面的话 本文将详细介绍MongoDB数据库索引构建情况分析 概述 创建索引可以加快索引相关的查询,但是会增加磁盘空间的消耗,降低写入性能.这时,就需要评判当前索引的构建情况是否合理.有4种方法可以使 ...
- 使用 NLog 给 Asp.Net Core 做请求监控
为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口.代码.数据表,做有针对性的优化可以提高效率.在 asp.ne ...
随机推荐
- java面试一日一题:讲下在什么情况下会发生类加载
问题:请讲下在什么情况下会发生类加载? 分析:该问题主要考察对java中类加载的知识,什么是类加载,为什么会发生类加载,什么情况下发生类加载? 回答要点: 主要从以下几点去考虑 1.什么是类加载: 2 ...
- Qt获取一张图片的平均色(主色调)
这两天在一个小工具中想做一个图标的发光效果,用固定颜色做出来效果很丑,于是想到此方法,得到图标的主色调后,将颜色调亮,并设置为阴影颜色,从而达到类似发光的效果. 本文章主要在于得到一张图片的平均色,并 ...
- 今日浅谈循环 for与while
昨天写的条件分支结构与今日写的循环是编程两个最基本的也非常重要的个结构 for循环 for循环可以从一个元组(tuple),列表(list),字典(dict),集合(set),字符串(string') ...
- hdu3460 字典树(打印机)
题意: 给你一些名字,让你用一台打印机去打印这些名字,打印机只有三个操作 (1)打印的都是小写字母 (2)每次可以在当前字母的后面加一位,或删除一位. (3)打印当前串 问你最少多少步可 ...
- nodejs-Buffer(缓冲区)
Node.js Buffer(缓冲区) JavaScript 语言自身只有字符串数据类型,没有二进制数据类型. 但在处理像TCP流或文件流时,必须使用到二进制数据.因此在 Node.js中,定义了一个 ...
- 在AWS Glue中使用Apache Hudi
1. Glue与Hudi简介 AWS Glue AWS Glue是Amazon Web Services(AWS)云平台推出的一款无服务器(Serverless)的大数据分析服务.对于不了解该产品的读 ...
- (二)SQL语句
语法规则 不区分大小写,但是建议关键字大写,表名.列名小写 SELECT * FROM user; 支持多行编写sql语言(在SQLyog中可以用F12来快速格式化语句) # 查询cno=20201/ ...
- Redis6.x学习笔记(一)
前言 最近学习Redis6.x,特做笔记以备忘,与大家共学.课程是从私塾在线下载的,他们把架构师课程都放出来了,大家可以去下载学习,不要钱的,地址是http://t.hk.uy/eac,课程很不错,值 ...
- Educational Codeforces Round 92 (Rated for Div. 2)
A.LCM Problem 题意:最小公倍数LCM(x,y),处于[l,r]之间,并且x,y也处于[l,r]之间,给出l,r找出x,y; 思路:里面最小的最小公倍数就是基于l左端点的,而那个最小公倍数 ...
- [技术博客]iview组件样式踩坑记录
[技术博客]iview组件样式踩坑记录 iview官方文档. 在本次项目开发中,前端项目主要使用vue框架+iview组件构建,其中iview组件在使用过程中遇到了许多官方文档中没有明确说明或是很难注 ...