前言

最近项目上每天间断性捕获到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请求异常详细情况分析的更多相关文章

  1. Web Api 中Get 和 Post 请求的多种情况分析

    转自:http://www.cnblogs.com/babycool/p/3922738.html 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用J ...

  2. Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析

    Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...

  3. [1.6W字] 浏览器跨域请求限制的详细原理分析&寻找一种最简单的方式实现XHR跨域(9种方法, 附大招可以纯前端实现跨域!)

    Title/ 浏览器跨域(CrossOrigin)请求的原理, 以及解决方案详细指南 #flight.Archives011 序: 最近看到又有一波新的创作活动了, 官方给出的话题中有一个" ...

  4. 【ASP.NET Core】处理异常--转

    老周写的[ASP.NET Core]处理异常非常的通俗易懂,拿来记录下. 转自老周:http://www.cnblogs.com/tcjiaan/p/8461408.html 今天咱们聊聊有关异常处理 ...

  5. .Net Core HttpClient处理响应压缩

    前言     在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采 ...

  6. IIS提示“异常详细信息: System.Runtime.InteropServices.ExternalException: 无法执行程序”

    先来看错误提示: 无法执行程序.所执行的命令为 "C:/Windows/Microsoft.NET/Framework/v3.5/csc.exe" /noconfig /fullp ...

  7. 在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程)

    在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程) 原文链接:http://www.360doc.com/content/14/1117/10/16948208_42571794 ...

  8. MongoDB数据库索引构建情况分析

    前面的话 本文将详细介绍MongoDB数据库索引构建情况分析 概述 创建索引可以加快索引相关的查询,但是会增加磁盘空间的消耗,降低写入性能.这时,就需要评判当前索引的构建情况是否合理.有4种方法可以使 ...

  9. 使用 NLog 给 Asp.Net Core 做请求监控

    为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口.代码.数据表,做有针对性的优化可以提高效率.在 asp.ne ...

随机推荐

  1. EhCache缓存使用教程

    文章发表在我的博客上:https://blog.ysboke.cn/archives/124.html 什么是ehcache 纯Java的进程内缓存,直接在JVM虚拟机中缓存,速度非常快.缓存有两级, ...

  2. 也谈如何写一个Webserver(-)

    关于如何写一个Webserver,很多大咖都发表过类似的文章.趁着这个五一假期,我也来凑个份子. 我写Webserver的原因,还得从如何将http协议传送的消息解析说起.当时,我只是想了解一下htt ...

  3. Redis 面试必备知识点

    1.Redis 简介 是一个用 C 语言开发的,高性能的键值对数据库. 数据存在于内存,读写速度快. 可用来做缓存.分布式锁.消息队列. 提供多种数据类型来支持不同的业务场景. 支持事务.持久化.Lu ...

  4. 022- Java算数运算符

    算数运算符有哪些: 输入以下代码: public class Operator01 { public static void main(String[]args){ int a = 10; int b ...

  5. Nginx 负载均衡方案

    轮询 根据Nginx配置文件中的顺序,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除. upstream web { server server1; serve ...

  6. Android平台dalvik模式下java Hook框架ddi的分析(1)

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75710411 一.前 言 在前面的博客中已经学习了作者crmulliner编写的, ...

  7. Python中的socket网络模块

    目录 Socket 服务端(server.py) 客户端(client.py) socket中的一些常用方法 Socket 对象(内建)方法 Python Internet 模块 Python3 提供 ...

  8. UVA11464偶数矩阵

    题意:       给你一个n*n的01矩阵,你的你的任务是吧尽量少的0变成1,使得每个元素的上下左右之和均为偶数(如果有的话),比如 0 0 0         0 1 0 1 0 0  ---&g ...

  9. 【编译原理】求First和Follow

    写这篇博客的原因,是因为考试前以为自己已经将这个问题弄清楚了,但是,考试的时候,发现自己还是不会,特别是求follow集合.虽然考试结束了,希望屏幕前的你,可以真正理解这个问题. 码字和做视频都不容易 ...

  10. Mongodb安装配置以及在thinkphp中的使用

    1.Mongodb安装 Mongodb安装参照此文档: https://www.runoob.com/mongodb/mongodb-window-install.html php的mongodb扩展 ...