日常 Bug 排查系列都是一些简单 Bug 的排查。笔者将在这里介绍一些排查 Bug 的简单技巧,同时顺便积累素材。
Bug 现场
最近碰到一个问题,一台机器上的连接数在达到一定连接数 (大概 4.5W) 连接数之后会突然急速下降到几百。在应用上的表现就是大量的连接报错,系统失去响应,如下图所示:
思路
思路 1: 第一步肯定是怀疑代码写错了,笔者看了下,使用的是成熟的框架,不是自己操作的连接,那么代码的问题应该较小。思路 2:那么笔者就开始怀疑是内核的限制,例如文件描述符到顶了之类,但这又有一个矛盾点。一旦是内核对连接数量限制的话,应该是连接数到达一定程度就涨不上去,而不是连接数跳水式下降。思路 2.1: 进一步,笔者就开始想,很有可能是某个间接资源的限制导致到达这个瓶颈后,所有的连接获取这个资源获取不到而导致全部报错。再结合 TCP 连接消耗的资源无非就是 CPU / 内存 / 带宽。
监控信息
有了上面的思路,我们就可以观察相关监控信息了。 CPU 监控:CPU 消耗很高达到了将近 70%,但获取不到 CPU 一般只会导致响应变慢,和问题现象不匹配。 带宽监控:带宽利用率达到了 50%,这个带宽利用率算不上高。 内存监控:确实使用了大量的内存,RSS 达到了 26G,但是比起 128G 的内存而言,这点消耗量显然不可能成为瓶颈。 好了,看了这三个数据之后,就发现系统的资源消耗还称不上达到瓶颈。但是,笔者从一开始就怀疑内存的使用可能触发了某个特殊的瓶颈。因为只有内存资源申请不到之后,TCP 连接才有可能直接报错进而 Drop 连接。
TCP 监控信息
当传统的监控已经不足以分析我们问题的时候,笔者就直接掏出针对 TCP 问题最有效的统计命令了,祭出法宝:
笔者在这条命令的输出中详细的观察 TCP 以及 TCP 内存相关的输出项,定睛一看,就发现一个很不寻常的地方:
这个输出就和笔者对于内存限制的猜想完全对应起来了。TCP 内存不够了,导致读取或者写入数据的时候申请内存失败进而将 TCP 连接本身给 Drop 了。
修改内核参数
因为笔者之前详细的阅读过 Linux TCP 的源代码以及其所有的可调整的内核参数。所以对 TCP 的内存限制有映像。有了 GPT 之后,只需要知道一个大致的方向就好了,直接问 GPT 就给出了答案,就是 tcp_mem 这个参数。
这三个值分别代表了 tcp 对于内存在不同阈值下的不同使用策略,单位是页,也就是 4KB。具体解释可以直接去问 GPT,在此就不赘述了。核心就是 TCP 消耗的内存总量在大于第三个值也就是 3144050 (12G,占 128G 内存的 9.35%) 的时候 TCP 就开始由于内存申请不到而 Drop 连接。而对应的应用由于每个请求高达好几 M 确实会让每个 TCP 连接消耗大量的内存。在内存消耗过程中一旦超限,那么 TCP 连接就会被内核强制 Drop,这也解释了为什么基本所有连接在很短的时间内就跳水式 Drop,因为他们都在不停申请内存,而达到临界阈值后全部都报错,进而整个系统的所有连接都关闭导致系统失去响应。如下图所示:
知道是这个问题就很简单了,直接将 tcp_mem 调大即可:
调整后系统保持稳定
在经过响应的内核调整之后,系统的连接数超过了 5W 之后依旧保持稳定。这时候我们观察相关的 TCP 消耗内存页的输出:
从这个输出我们可以看到系统平稳运行后,其常态使用的内存页数量 mem 为 4322151 已经远大于之前的 3144050,这也从侧面验证了笔者的判断。
对应的内核栈
在此记录下对应的 Linux 内核栈
可以看到当 allocated 大于相关的内存 limit 之后 Linux Kernel 会将此 TCP 连接直接 Drop。
总结
笔者在了解清楚 Bug 现场之后,大概花了 20 分钟就定位到了是 TCP 内存瓶颈的问题,然后借助 GPT 非常快速的找到了相关解决方案。不得不说 GPT 能够大幅加速我们搜索的过程,笔者个人感觉可以在很大程度上替代搜索引擎。但喂给 GPT 的 Prompt 还是需要通过 Bug 现场以及一定的经验来构造,它代替不了你的思考,但能大幅加速信息的检索。