前言
日常 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连接的各种统计参数,很多问题都可以通过其输出获得线索
netstat -s
笔者在这条命令的输出中详细的观察 TCP 以及 TCP 内存相关的输出项,定睛一看,就发现一个很不寻常的地方:
...
TcpExt:
TCP ran low on memoery 19 times
......
这个输出就和笔者对于内存限制的猜想完全对应起来了。TCP 内存不够了,导致读取或者写入数据的时候申请内存失败进而将 TCP 连接本身给 Drop 了。
修改内核参数
因为笔者之前详细的阅读过 Linux TCP 的源代码以及其所有的可调整的内核参数。所以对 TCP 的内存限制有映像。有了 GPT 之后,只需要知道一个大致的方向就好了,直接问 GPT 就给出了答案,就是 tcp_mem 这个参数。
cat /proc/sys/net/ipv4/tcp_mem
1570347 2097152 3144050
这三个值分别代表了 tcp 对于内存在不同阈值下的不同使用策略,单位是页,也就是 4KB。具体解释可以直接去问 GPT,在此就不赘述了。核心就是 TCP 消耗的内存总量在大于第三个值也就是 3144050 (12G,占 128G 内存的 9.35%) 的时候 TCP 就开始由于内存申请不到而 Drop 连接。而对应的应用由于每个请求高达好几 M 确实会让每个 TCP 连接消耗大量的内存。
在内存消耗过程中一旦超限,那么 TCP 连接就会被内核强制 Drop,这也解释了为什么基本所有连接在很短的时间内就跳水式 Drop,因为他们都在不停申请内存,而达到临界阈值后全部都报错,进而整个系统的所有连接都关闭导致系统失去响应。如下图所示:
图片
知道是这个问题就很简单了,直接将 tcp_mem 调大即可:
cat /proc/sys/net/ipv4/tcp_mem
3570347 6097152 9144050
调整后系统保持稳定
在经过响应的内核调整之后,系统的连接数超过了 5W 之后依旧保持稳定。这时候我们观察相关的 TCP 消耗内存页的输出:
cat /proc/net/sockstat
TCP: inuse xxx orphan xxx tw xxx alloc xxxx mem 4322151
从这个输出我们可以看到系统平稳运行后,其常态使用的内存页数量 mem 为 4322151 已经远大于之前的 3144050,这也从侧面验证了笔者的判断。
对应的内核栈
在此记录下对应的 Linux 内核栈
tcp_v4_do_rcv
|->tcp_rcv_established
|->tcp_data_queue
|->tcp_data_queue
|->tcp_try_rmem_schedule
|->sk_rmem_schedule
|->sk_rmem_schedule
|->__sk_mem_raise_allocated
|-> /* Over hard limit. */
if (allocated > sk_prot_mem_limits(sk, 2))
goto suppress_allocation;
|->goto drop:
tcp_drop(sk,skb)
可以看到当 allocated 大于相关的内存 limit 之后 Linux Kernel 会将此 TCP 连接直接 Drop。
总结
笔者在了解清楚 Bug 现场之后,大概花了 20 分钟就定位到了是 TCP 内存瓶颈的问题,然后借助 GPT 非常快速的找到了相关解决方案。不得不说 GPT 能够大幅加速我们搜索的过程,笔者个人感觉可以在很大程度上替代搜索引擎。但喂给 GPT 的 Prompt 还是需要通过 Bug 现场以及一定的经验来构造,它代替不了你的思考,但能大幅加速信息的检索。