记一次服务器 CPU 飙到 100% 的排查过程

Cold 发布于 2019-02-23 36 次阅读 520 字 预计阅读时间: 2 分钟 问题排查


周五晚上十一点,手机收到云监控告警:CPU 使用率 98%,持续 5 分钟。第一反应是被打了?登录服务器开始查。把整个过程记下来,CPU 飙高的排查思路基本都能套用。

第一步:先看是谁在吃 CPU

top   # 按 P 键按 CPU 排序

看到一个 java 进程吃满了 190%+(机器 2 核)。不是陌生进程,是业务进程本身,先排除了挖矿(挖矿通常是伪装成系统进程的陌生东西)。再看负载是 CPU 密集还是 IO 等待:top 里 %wa 很低、%us 很高,纯计算型。

第二步:定位到线程

top -Hp 21409        # 看 21409 这个进程里的线程
# 找到最吃的线程 PID,比如 21537
printf '%x
' 21537   # 转成十六进制 0x5421

然后拿线程栈。Java 进程直接 jstack:

jstack 21409 | grep -A 30 0x5421

栈帧显示这个线程卡在一个正则匹配上,而且连抓三次栈都在同一个位置——典型的正则回溯失控。去代码里搜,是新上的一个手机号校验,正则写成了:

^(1[3-9]\d{1,}\d*)$

看着没毛病,但当输入是超长数字串(有用户把订单号粘进去了,160 多位)时,\d{1,}\d* 这种写法会让引擎疯狂回溯。Java 正则没有自动的回溯上限,就 CPU 打满了。

第三步:止血与修复

  • 临时止血:限流该接口,CPU 慢慢回落,没有重启服务
  • 修复:正则改成 1[3-9]\d{9} 精确匹配,并且在入口处先校验长度不超过 11 位
  • 复盘:给所有接收用户输入的正则加了长度前置校验。正则这东西,写的时候只想着"匹配什么",很少想"不匹配什么的时候要算多久"

非 Java 的场景同理:Python 可以用 py-spy dump,Go 用 pprof,C/C++ 上 perf top。套路是一样的——先进程、再线程、再栈帧,三步之内必见真相。

和喜欢的一切在一起~
最后更新于 2019-02-23