HttpClient 设置不当引起的一次雪崩!

一. 事件背景

我最近运维了一个网上的实时接口服务,最近常常出现Address already in use (Bind failed)的问题。java

很明显是一个端口绑定冲突的问题,因而大概排查了一下当前系统的网络链接状况和端口使用状况,发现是有大量time_wait的链接一直占用着端口没释放,致使端口被占满(最高的时候6w+个),所以HttpClient创建链接的时候会出现申请端口冲突的状况。git

具体状况以下:github

time_wait特征

因而为了解决time_wait的问题,网上搜索了些许资料加上本身的思考,因而认为能够经过链接池来保存tcp链接,减小HttpClient在并发状况下随机打开的端口数量,复用原来有效的链接。可是新的问题也由链接池的设置引入了。面试

二. 问题过程

在估算链接池最大链接数的时候,参考了业务高峰期时的请求量为1分钟1.2w pv,接口平响为1.3s(复杂的广告推广效果模拟系统,在这种场景平响高是业务所需的缘由)。spring

所以qps为12000*1.3\60=260后端

而后经过观察了业务日志,每次链接创建耗时1.1s左右, 再留70%+的上浮空间(怕链接数设置小出系统故障),最大链接数估计为2601.1*1.7约等于500。xcode

为了减小对以前业务代码最小的改动,保证优化的快速上线验证,仍然使用的是HttpClient3.1 的MultiThreadedHttpConnectionManager,而后在线下手写了多线程的测试用例,测试了下并发度确实能比没用线程池的时候更高,而后先在咱们的南京机房小流量上线验证效果,效果也符合预期以后,就开始整个北京机房的转全。结果转全以后就出现了意料以外的系统异常。。。网络

三. 案情回顾

在当天晚上流量转全以后,一块儿状况符合预期,可是到了次日早上就看到用户群和相关的运维群里有一些人在反馈实况页面打不开了。这个时候我在路上,让值班人帮忙先看了下大概的状况,定位到了耗时最高的部分正是经过链接池调用后端服务的部分,因而能够把这个突发问题的排查思路大体定在围绕线程池的故障来考虑了。多线程

因而等我到了公司,首先观察了一下应用总体的状况:并发

  • 监控平台的业务流量表现正常,可是部分机器的网卡流量略有突增
  • 接口的平响出现了明显的上升
  • 业务日志无明显的异常,不是底层服务超时的缘由,所以平响的缘由确定不是业务自己
  • 发现30个机器实例居然有9个出现了挂死的现象,其中6个北京实例,3个南京实例

四. 深刻排查

因为发现了有近 1/3的实例进程崩溃,而业务流量没变,因为RPC服务对provider的流量进行负载均衡,因此引起单台机器的流量升高,这样会致使后面的存活实例更容易出现崩溃问题,因而高优看了进程挂死的缘由。

因为极可能是修改了HttpClient链接方式为链接池引起的问题,最容易引发变化的确定是线程和CPU状态,因而当即排查了线程数和CPU的状态是否正常

一、CPU状态

CPU特征

如图可见Java进程占用cpu很是高,是平时的近10倍

二、线程数监控状态:

图中能够看到多个机器大概在10点初时,出现了线程数大量飙升,甚至超出了虚拟化平台对容器的2000线程数限制(平台为了不机器上的部分容器线程数太高,致使机器总体夯死而设置的熔断保护),所以实例是被虚拟化平台kill了。以前为何以前在南京机房小流量上线的时候没出现线程数超限的问题,应该和南京机房流量较少,只有北京机房流量的1/3有关。

接下来就是分析线程数为啥会快速积累直至超限了。这个时候我就在考虑是不是链接池设置的最大链接数有问题,限制了系统链接线程的并发度。为了更好的排查问题,我回滚了线上一部分的实例,因而观察了下线上实例的 tcp链接状况和回滚以后的链接状况

回滚以前tcp链接状况:

回滚以后tcp链接状况:

发现链接线程的并发度果真小不少了,这个时候要再确认一下是不是链接池设置致使的缘由,因而将没回滚的机器进行jstack了,对Java进程中分配的子线程进行了分析,总于能够确认问题

jstack状态:

从jstack的日志中能够很容易分析出来,有大量的线程在等待获取链接池里的链接而进行排队,所以致使了线程堆积,所以平响上升。因为线程堆积越多,系统资源占用越厉害,接口平响也会所以升高,更加重了线程的堆积,所以很容易出现恶性循环而致使线程数超限。

那么为何会出现并发度设置太小呢?以前已经留了70%的上浮空间来估算并发度,这里面一定有蹊跷!

因而我对源码进行了解读分析,发现了端倪:

如MultiThreadedHttpConnectionManager源码可见,链接池在分配链接时调用的doGetConnection方法时,对可否得到链接,不只会对我设置的参数maxTotalConnections进行是否超限校验,还会对maxHostConnections进行是否超限的校验。

因而我马上网上搜索了下maxHostConnections的含义:每一个host路由的默认最大链接,须要经过setDefaultMaxConnectionsPerHost来设置,不然默认值是2

因此并非我对业务的最大链接数计算失误,而是由于不知道要设置DefaultMaxConnectionsPerHost而致使每一个请求的Host并发链接数只有2,限制了线程获取链接的并发度(因此难怪刚才观察tcp并发度的时候发现只有2个链接创建 😃 )

五. 案情总结

到此此次雪崩事件的根本问题已完全定位,让咱们再次精炼的总结一下这个案件的全过程:

  1. 链接池设置错参数,致使最大链接数为2
  2. 大量请求线程须要等待链接池释放链接,出现排队堆积
  3. 夯住的线程变多,接口平响升高,占用了更多的系统资源,会加重接口的耗时增长和线程堆积
  4. 最后直至线程超限,实例被虚拟化平台kill
  5. 部分实例挂死,致使流量转移到其余存活实例。其余实例流量压力变大,容易引起雪崩

关于优化方案与如何避免此类问题再次发生,我想到的方案有3个:

  1. 在作技术升级前,要仔细熟读相关的官方技术文档,最好不要遗漏任何细节
  2. 能够在网上找其余可靠的开源项目,看看别人的优秀的项目是怎么使用的。好比github上就能够搜索技术关键字,找到一样使用了这个技术的开源项目。要注意挑选质量高的项目进行参考
  3. 先在线下压测,用控制变量法对比各种设置的不一样状况,这样把全部问题在线下提早暴露了,再上线内心就有底了

如下是我设计的一个压测方案:

a. 测试不用链接池和使用链接池时,分析总体能承受的qps峰值和线程数变化

b. 对比setDefaultMaxConnectionsPerHost设置和不设置时,分析总体能承受的qps峰值和线程数变化

c. 对比调整setMaxTotalConnections,setDefaultMaxConnectionsPerHost 的阈值,分析总体能承受的qps峰值和线程数变化

d. 重点关注压测时实例的线程数,cpu利用率,tcp链接数,端口使用状况,内存使用率

综上所述,一次链接池参数致使的雪崩问题已经从分析到定位已所有解决。在技术改造时咱们应该要谨慎对待升级的技术点。在出现问题后,要重点分析问题的特征和规律,找到共性去揪出根本缘由。

原文连接:https://blog.csdn.net/qq_16681169/article/details/94592472

版权声明:本文为CSDN博主「zxcodestudy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处连接及本声明。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

3.阿里 Mock 工具正式开源,干掉市面上全部 Mock 工具!

4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

以为不错,别忘了随手点赞+转发哦!