线上 CPU 100% 排查:正则表达式导致的性能问题
本文是线上问题实战录系列的第 3 篇
叙事框架:现象 → 排查过程 → 根因 → 修复 → 预防
问题现象
2026 年 6 月 18 日下午 3 点 15 分,生产监控告警群突然炸了:
- CPU 100%:身份验证服务
auth-serviceCPU 使用率持续 100%,已持续 12 分钟 - 接口超时:
/api/v1/auth/validatep99 响应时间从 50ms 飙升到 2.3 秒 - 错误率飙升:错误率从 0.1% 上升到 3.2%,部分请求直接 504
- 进程不稳定:Pod 已重启 3 次(OOMKilled)

排查过程
第一步:定位高 CPU 进程
登录生产服务器,执行 top,发现 PID=24589 的 Java 进程占满了 1 个核,RSS 4.5GB:

第二步:定位消耗 CPU 的线程
top -Hp 24589
TID=24650 占 99% CPU,其他 186 个线程几乎空闲。转十六进制:printf '%x\n' 24650 → 0x605a。

第三步:分析 Java 线程栈
jstack 24589 > /tmp/jstack.txt
grep -A 40 'nid=0x605a' /tmp/jstack.txt
输出显示线程栈卡死在正则表达式匹配中,89 帧都在 Pattern$BmpCharProperty.match 之间来回跳——典型的正则回溯:

第四步:用 Arthas 快速验证
一秒就查出来了,和 jstack 结果一致,但快得多:

第五步:找到问题代码
String.matches(pattern) 等价于 Pattern.compile(pattern).matcher(input).matches()。每次调用都重新编译同一个正则表达式,在高并发下 CPU 直接打满:

根因分析
今天上午 10 点上线的代码变更引入了 isValidPhoneV1。该方法在每次收到验证请求时都通过 String.matches() 编译正则表达式:
String.matches() → Pattern.compile() → 编译正则 → 创建 Pattern 对象 → 创建 Matcher 对象 → 执行匹配
↑ 每次请求重复执行整个过程
在高并发下(100 TPS),不断重复的 Pattern.compile() 导致 CPU 完全被正则编译和匹配占据。
修复方案
将正则表达式预编译为 static final 常量:


验证结果
JMH 基准测试

HTTP 压测对比

| 指标 | V1(修复前) | V2(修复后) | 提升倍数 |
|---|---|---|---|
| TPS | 6,067 | 28,340 | 4.67x |
| 平均响应时间 | 16.48ms | 3.53ms | ↓78% |
| P99 响应时间 | 196ms | 28ms | ↓86% |
| CPU 使用率 | 99.2% | 12.3% | ↓86% |
避坑建议
1. 正则表达式使用规范
- 禁止在循环/高并发路径中使用
String.matches()、Pattern.matches()、String.split()等会触发隐式编译的方法 - 所有正则表达式必须声明为
static final Pattern常量 - 复杂正则表达式的回溯性能需要使用工具验证(如 regex101.com)
2. 上线流程优化
- 修改高并发路径的代码必须附带性能压测数据
- 代码审查 checklist 中增加正则表达式使用检查项
3. 监控告警优化
- 增加对线程级别的监控,快速定位 CPU 消耗源
- 配置 arthas 到基础镜像中,方便即时诊断
4. 诊断工具推荐
- 快速定位:
top -Hp→printf '%x\n'→jstack→ grep nid - 更快的定位:
arthas thread -n 3 - 性能分析:JMH 基准测试、async-profiler 火焰图
- 压测工具:Apache Benchmark (ab)、JMeter
附:完整命令清单
进程与线程级 CPU 排查
top -b -n 1 | head -20 # 查看进程 CPU 排行
top -b -H -p <pid> -n 1 | head -25 # 查看线程 CPU 排行
printf '%x\n' <tid> # TID 转十六进制
Java 诊断
jstack <pid> > /tmp/jstack.txt # 导出线程栈
grep -A 40 'nid=0x<nid>' /tmp/jstack.txt # 查看指定线程栈帧
thread -n 3 # Arthas 查看最忙线程
stack <class> <method> # Arthas 查看方法调用链
grep -n 'isValidPhone\\|String.matches\\|Pattern' src/main/java/com/opencao/demo/UserValidator.java # 定位问题代码
压测验证
ab -n 10000 -c 100 -T 'application/json' -p request.json http://auth-service:8080/api/v1/auth/validate/v1 # 修复前
ab -n 10000 -c 100 -T 'application/json' -p request.json http://auth-service:8080/api/v1/auth/validate/v2 # 修复后
mvn test -Dtest=RegexBenchmark -pl . # JMH 基准测试
📖 全文带可复现 Demo 和排查截图 🔗 个人博客:https://opencao.cn 📺 公众号:Ai拆代码的曹操 🌟 知识星球:源阅会 (82877104)