SRE 生产故障实录
Linux CPU 飙高排查实战
从监控告警到精准定位,还原一次核心网关服务 CPU 100% 的硬核排查之旅
凌晨 02:15,核心交易网关突然触发 CPU 使用率告警。监控大盘显示,该节点 CPU Usage 瞬间从 35% 飙升至 92%,且持续高位震荡。伴随现象是接口 P99 延迟从 120ms 恶化至 2.5s,部分请求直接超时熔断。值得注意的是,此时业务 QPS 并未出现突增,内存使用率平稳,网络带宽正常,排除了 DDoS 攻击和内存泄漏 OOM 的可能。
值班 SRE 第一时间介入,登录跳板机执行基础巡检。通过 `top` 观察,发现整体 CPU 分布中 `us`(用户态)占比高达 88%,`sy`(内核态)仅 3%,`wa`(IO 等待)接近 0%。这明确指向了用户空间代码执行效率问题,而非磁盘 I/O 瓶颈或内核中断风暴。
面对单核打满的进程,直接重启虽能快速止血,但会掩盖根因并可能导致二次故障。我们决定采用“进程→线程→函数调用栈”的标准排查路径。
🚨 关键指标快照
PID: 28471 (gateway-api) | CPU: 91.2% | STATE: S (可中断睡眠) / R (运行) 交替 | 线程数: 42
第一步:定位高负载线程(TID)使用 `pidstat` 抓取进程内各线程的 CPU 消耗明细。命令如下:
$ pidstat -p 28471 -t 1 303:15:22 PM UID TGID TID %usr %system %guest %CPU CPU Command03:15:22 PM 0 28471 - 12.50 0.50 0.00 13.00 2 gateway-api03:15:22 PM 0 - 28475 89.00 0.20 0.00 89.20 2 |__gateway-api03:15:22 PM 0 - 28481 2.10 0.10 0.00 2.20 1 |__gateway-api03:15:22 PM 0 - 28482 1.80 0.10 0.00 1.90 3 |__gateway-api03:15:22 PM 0 - 28483 0.50 0.05 0.00 0.55 0 |__gateway-api
输出清晰表明,TID 为 `28475` 的线程吃掉了近 90% 的 CPU 资源。这是一个典型的单线程热点现象,其他线程处于相对空闲状态,说明问题并非全局性负载,而是特定业务逻辑卡死。
第二步:抓取调用栈与采样分析由于该服务采用 Go 语言编写,常规 `jstack` 不适用。我们使用 `perf` 进行内核级 CPU 周期采样,直接映射到函数符号。首先将 TID 转换为十六进制:
$ printf "%x\n" 284756f3b# 启动 10 秒 perf 采样,抓取该进程全量调用栈$ sudo perf record -g -p 28471 sleep 10[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 4.21 MB perf.data (1845 samples) ]# 交互式查看热点函数分布$ sudo perf report -n --stdio | head -n 30# Overhead Command Shared Object Symbol 78.42% gateway-api gateway-api main.(*LogMiddleware).FormatWithRegex 8.15% gateway-api [kernel.kallsyms] __schedule 4.21% gateway-api [kernel.kallsyms] copy_user_enhanced_fast_string 2.88% gateway-api libc-2.28.so __malloc_consolidate 1.92% gateway-api [kernel.kallsyms] sysret_check
`perf report` 的结果极具指向性:`78.42%` 的 CPU 时间集中在 `main.(*LogMiddleware).FormatWithRegex` 函数上。这直接锁定了代码位置:日志中间件中的正则格式化逻辑。
结合代码仓库回溯,发现该日志中间件在上线前引入了一段用于清洗特殊字符的正则表达式:
`^([a-zA-Z0-9_\-]+\.)*[a-zA-Z0-9_\-]+$`
该正则在常规输入下性能良好,但在遇到特定构造的异常输入(如大量重复字符后跟不匹配结尾:`aaaaaaaaaaaaaaaaaaaa!`)时,触发了 NFA 正则引擎的灾难性回溯(Catastrophic Backtracking)。引擎在匹配失败后会尝试指数级数量的分支组合,导致单次匹配耗时从微秒级飙升至数秒级,且完全在用户态执行,持续占用 CPU 时间片不释放。
此时恰逢上游某老旧客户端因配置错误,批量发送了异常 Payload,直接引爆了该逻辑漏洞。由于 Go 的 Goroutine 调度机制,该死循环线程持续抢占 CPU 时间片,导致同节点的其他健康 Goroutine 饥饿,最终表现为整体服务延迟飙升与 CPU 打满。值得注意的是,该函数未做任何超时控制或输入长度校验,彻底放大了攻击面。
💡 SRE 经验提示
正则表达式不是万能的。在生产环境中使用正则前,必须通过自动化工具进行回溯深度检测。任何未设超时(Timeout)或步数限制(Backtrack Limit)的正则匹配,都是潜在的 CPU 炸弹。
定位根因后,我们按照“先恢复业务,后修复代码”的原则执行操作:
🔹 紧急止血(5分钟内)
1. 通过网关层 WAF 策略临时拦截包含超长连续重复字符的请求特征。2. 对问题节点执行平滑摘流(`drain`),利用 K8s `preStop` 钩子完成优雅下线。3. 扩容备用节点,将流量切至健康池,P99 延迟在 3 分钟内恢复至 150ms 以内。
🛠 长期修复与架构优化
1. 替换解析逻辑:将原正则匹配替换为确定性状态机(DFA)实现或纯字符串扫描算法,消除指数级回溯可能。2. 增加熔断与超时控制:为所有字符串解析操作添加硬编码超时(如 5ms),超时直接 fallback 到原始日志或丢弃,防止单点故障蔓延。3. 代码审查卡点:在 CI/CD 流水线集成静态分析扫描,自动拦截高风险正则模式,并强制要求附带性能基准测试报告。
修复前后性能对比数据如下:
本次故障虽由一行正则引发,但暴露了我们在代码质量管控与可观测性建设上的短板。作为 SRE,我们不能止步于“修好代码”,必须将经验固化为平台能力:
1. 建立 CPU 飙高黄金排查路径top/pidstat → 定位 TID → 转十六进制 → perf/pprof/jstack → 映射源码 → 修复验证。任何偏离此路径的“盲猜式重启”都应被严格管控,确保故障现场数据完整留存。
2. 强化左移防御机制在研发侧推行“防御性编程”。所有涉及字符串处理、网络解析、加解密的模块,必须通过模糊测试(Fuzzing)覆盖异常输入边界。CI 流水线必须阻断未通过性能基准测试的合并请求,将稳定性问题拦截在测试环境。
3. 自动化诊断工具赋能为降低一线值班人员的认知负荷,我们将排查命令封装为标准化脚本,实现“一键采集、自动解析、生成报告”。后续将集成至内部运维平台,实现告警触发即自动拉取快照,大幅缩短 MTTR(平均恢复时间)。
以下脚本已内置 `top`、`pidstat`、`perf` 自动化调用逻辑,支持自动采集高 CPU 线程堆栈并输出十六进制 TID 列表,可直接在生产环境安全执行(需 `root` 或 `perf` 权限):
#!/bin/bash# Linux CPU 飙高自动化诊断脚本 v1.2# 用法: sudo bash top_cpu_diag.sh [PID]set -euo pipefailTARGET_PID=${1:-""}if [[ -z "$TARGET_PID" ]]; then echo "❌ 未传入 PID,请手动指定: $0 <PID>" exit 1fiecho "🔍 开始诊断进程 PID: $TARGET_PID"echo "----------------------------------------"echo "📊 1. 抓取高负载线程 (Top 5 TIDs)..."pidstat -p "$TARGET_PID" -t 1 3 2>/dev/null | awk 'NR>5{print $4, $3, $11}' | sort -rn | head -5 | while read CPU TGID TID; do HEX_TID=$(printf "%x\n" "$TID") echo " [TID:$TID] CPU:${CPU}% -> 0x${HEX_TID}"doneecho "----------------------------------------"echo "📈 2. 执行 5 秒 perf 采样 (生成 perf.data)..."if command -v perf >/dev/null 2>&1; then sudo perf record -g -p "$TARGET_PID" sleep 5 >/dev/null 2>&1 echo "✅ 采样完成。运行以下命令查看热点:" echo " sudo perf report -n --stdio | head -n 20"else echo "⚠️ 未找到 perf 工具,请安装 linux-tools-common" echo " 降级方案: sudo strace -p $TARGET_PID -c -w 5"fiecho "----------------------------------------"echo "💡 排查完成。请将 perf.data 与业务日志打包提交给 SRE 团队。"⚙️ 使用注意
`perf` 采样会产生极小开销(<2%),建议控制在 10 秒内。若生产环境严格限制 `ptrace` 权限,可替换为 `strace -c` 统计系统调用耗时分布,或依赖应用层内置的 pprof/trace 端点。