共计 2920 个字符,预计需要花费 8 分钟才能阅读完成。
一. 事件背景
我最近运维了一个网上的实时接口服务,最近经常出现 Address already in use (Bind failed)的问题。
很显著是一个端口绑定抵触的问题,于是大略排查了一下以后零碎的网络连接状况和端口应用状况,发现是有大量 time_wait 的连贯始终占用着端口没开释,导致端口被占满(最高的时候 6w+ 个),因而 HttpClient 建设连贯的时候会呈现申请端口抵触的状况。
具体情况如下:
于是为了解决 time_wait 的问题, 网上搜寻了些许材料加上本人的思考, 于是认为能够通过连接池来保留 tcp 连贯, 缩小 HttpClient 在并发状况下随机关上的端口数量,复用原来无效的连贯。然而新的问题也由连接池的设置引入了。
二. 问题过程
在估算连接池最大连接数的时候,参考了业务高峰期时的申请量为 1 分钟 1.2w pv, 接口平响为 1.3s(简单的广告推广成果模拟系统,在这种场景平响高是业务所需的起因)。
因而 qps 为 12000*1.3\60=260
而后通过观察了业务日志,每次连贯建设耗时 1.1s 左右, 再留 70%+ 的上浮空间(怕连接数设置小出系统故障),最大连接数预计为 2601.1*1.7 约等于 500。
为了缩小对之前业务代码最小的改变,保障优化的疾速上线验证,依然应用的是 HttpClient3.1 的 MultiThreadedHttpConnectionManager,而后在线下手写了多线程的测试用例,测试了下并发度的确能比没用线程池的时候更高,而后先在咱们的南京机房小流量上线验证成果,成果也合乎预期之后,就开始整个北京机房的转全。后果转全之后就呈现了意料之外的零碎异样。。。
三. 案情回顾
在当天早晨流量转全之后,一起状况合乎预期,然而到了第二天早上就看到用户群和相干的运维群里有一些人在反馈实况页面打不开了。这个时候我在路上,让值班人帮忙先看了下大略的状况,定位到了耗时最高的局部正是通过连接池调用后端服务的局部,于是能够把这个突发问题的排查思路大抵定在围绕线程池的故障来思考了。
于是等我到了公司,首先察看了一下利用整体的状况:
- 监控平台的业务流量体现失常,然而局部机器的网卡流量略有突增
- 接口的平响呈现了显著的回升
- 业务日志无显著的异样,不是底层服务超时的起因,因而平响的起因必定不是业务自身
- 发现 30 个机器实例居然有 9 个呈现了挂死的景象,其中 6 个北京实例,3 个南京实例
四. 深刻排查
因为发现了有近 1/ 3 的实例过程解体,而业务流量没变,因为 RPC 服务对 provider 的流量进行负载平衡,所以引发单台机器的流量升高,这样会导致前面的存活实例更容易呈现解体问题,于是高优看了过程挂死的起因。
因为很可能是批改了 HttpClient 连贯形式为连接池引发的问题,最容易引起变动的必定是线程和 CPU 状态,于是立刻排查了线程数和 CPU 的状态是否失常
1、CPU 状态
如图可见 Java 过程占用 cpu 十分高,是平时的近 10 倍
2、线程数监控状态:
图中能够看到多个机器大略在 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 个连贯建设 😃)
五. 案情总结
到此这次雪崩事件的基本问题已彻底定位,让咱们再次精炼的总结一下这个案件的全过程:
- 连接池设置错参数,导致最大连接数为 2
- 大量申请线程须要期待连接池开释连贯,呈现排队沉积
- 夯住的线程变多,接口平响升高,占用了更多的系统资源,会加剧接口的耗时减少和线程沉积
- 最初直至线程超限,实例被虚拟化平台 kill
- 局部实例挂死,导致流量转移到其余存活实例。其余实例流量压力变大,容易引发雪崩
对于优化计划与如何防止此类问题再次发生,我想到的计划有 3 个:
- 在做技术升级前,要认真熟读相干的官网技术文档,最好不要脱漏任何细节
- 能够在网上找其余牢靠的开源我的项目,看看他人的优良的我的项目是怎么应用的。比方 github 上就能够搜寻技术关键字,找到同样应用了这个技术的开源我的项目。要留神筛选品质高的我的项目进行参考
- 先在线下压测,用控制变量法比照各类设置的不同状况,这样把所有问题在线下提前裸露了,再上线心里就有底了
以下是我设计的一个压测计划:
a. 测试不必连接池和应用连接池时,剖析整体能接受的 qps 峰值和线程数变动
b. 比照 setDefaultMaxConnectionsPerHost 设置和不设置时,剖析整体能接受的 qps 峰值和线程数变动
c. 比照调整 setMaxTotalConnections,setDefaultMaxConnectionsPerHost 的阈值,剖析整体能接受的 qps 峰值和线程数变动
d. 重点关注压测时实例的线程数,cpu 利用率,tcp 连接数,端口应用状况,内存使用率
综上所述,一次连接池参数导致的雪崩问题曾经从剖析到定位已全副解决。在技术改造时咱们应该要审慎看待降级的技术点。
在呈现问题后,要重点剖析问题的特色和法则,找到共性去揪出根本原因。
原文链接:https://blog.csdn.net/qq_1668…