线上诡异疑难Bug:接口压测早已停止,流量归零,但是应用CPU永久打满100%,机器持续告警,只能重启临时恢复。
无业务死循环、无慢SQL、无频繁GC、无热点Key,业务代码零问题,排查全程毫无头绪。
最终定位:不是业务代码问题,是Redisson客户端底层隐藏极端边界死循环Bug。
该问题概率极低,日常运行完全无感,只会在Redis网络异常、命令写入失败、连接归还重试链路触发;官方虽已修复,但需要大版本升级。绝大多数企业受限于Spring版本、中间件依赖,根本不敢升级Redisson。
本文手把手完整复盘:线程栈排查、源码逐行剖析死循环成因、官方PR修复思路、不升级依赖,本地源码热修复落地方案,看完彻底搞定该隐秘线上故障。
全链路压测完成,线上流量直接归零,无任何业务请求进入服务
应用CPU持续100%居高不下,不会自动回落,机器负载持续飙升
服务接口响应超时、心跳检测异常,业务近乎不可用
重启应用后立刻恢复正常,一段时间后大概率复现,无固定复现规律
起初依次排查常规CPU高占用诱因,全部排除:
✅ 无业务自定义死循环代码
✅ GC日志正常,无频繁FullGC、无内存泄漏
✅ 数据库无慢查询、无连接池打满
✅ Redis服务端指标完全正常,无CPU、内存、连接异常
所有业务层面监控全部正常,故障矛头直指第三方中间件客户端:Redisson。
执行命令,定位进程内高CPU线程:
#查看进程下所有线程CPU占用
top -H -p 应用pid观测结果:线程号189持续霸占CPU,占用率拉满。

Java线程dump堆栈中线程ID为16进制,转换:189 = 0xBD。
导出线程栈
#导出线程栈信息, pid是java进程id
jstack pid线程dump文件内容(找到nid=0xbd的内容)
"redisson-netty-2-6" #184 prio=5 os_prio=0 cpu=295856.81ms elapsed=3109.81s nid=0xbd runnable
java.lang.Thread.State: RUNNABLE
at org.redisson.client.handler.CommandsQueue.write(CommandsQueue.java:79)
// 后续一长串Netty、Redisson连接归还、命令写入、异常回调链路堆栈
...卡死位置固定:Redisson CommandsQueue 第79行,Netty工作线程一直处于RUNNABLE运行态,无限空转死循环,永久霸占CPU。

CommandsQueue.write 核心逻辑极简,采用AtomicBoolean做自旋锁控制命令串行写入:
// 原子标记锁
private final AtomicBoolean lock = new AtomicBoolean();
public void write(...) {
// 第79行:死循环自旋抢占锁
while (!lock.compareAndSet(false, true)) {
try {
// 执行Redis命令写入Netty通道
doWriteCommand();
}
finally {
// 第89行:释放锁,重置标记
lock.set(false);
}
}第一眼看上去代码完全没问题:CAS自旋抢锁,finally保证一定释放锁,标准无锁自旋写法。
但Redisson复杂异步回调链路下,出现了极端异常时序。
3. 既然定位到redisson的问题,本人向redisson作者提交了issue,
完整复现故障堆栈、描述问题场景,官方维护者直接给出精准定位与修复方案。
issue地址:https://github.com/redisson/redisson/issues/6153
下方为官方作者截图内容【原版回复原文】(一字未改,和后台截图完全一致):
mrniko(Redisson官方维护者)原话:The deadlock is that the CommandsQueue#write are invoked twice in one stack. Obviously, it cannot obtain the lock at the second time.
精准中文翻译:本次死循环问题根源为,同一个调用栈中,CommandsQueue#write方法被连续调用两次。线程第二次尝试获取锁时,永远获取不到锁,从而陷入无限自旋。
结合官方开发者复盘 + 完整调用链路堆栈,还原唯一触发时序:
一句话通俗总结:单线程可重入调用write,原生CAS非可重入锁不支持重入,锁无法释放,直接卡死死循环。
官方修复思路极其直白,直击痛点:
把不可重入的AtomicBoolean自旋锁,替换为JDK原生ReentrantLock可重入锁。
允许同一线程重复抢占锁,彻底解决单线程重入导致的永久自旋死循环。
线上生产环境无法直接升级高版本Redisson,原因:
新版Redisson依赖更高版本Spring、SpringBoot
工程整体Spring体系无法大范围升级,改动风险极高
回归测试工作量巨大,生产环境禁止贸然升级中间件大版本
不能升级版本,但是线上CPU100%故障必须解决,只能手动本地源码修复。
无需改动项目任何业务代码、无需升级任何依赖,直接覆盖Redisson原生类即可修复。
完全对齐官方PR#6203修复逻辑,自行替换锁实现:
删除原生 AtomicBoolean 自旋锁
替换为 ReentrantLock 可重入锁
保证同一线程重入write方法可以正常获取锁,杜绝无限自旋
❌ 旧版问题代码

✅ 本地修复后代码(对齐官方PR)
https://github.com/redisson/redisson/pull/6203

这不是常规业务Bug,是Redisson异步回调+Netty线程模型+非可重入自旋锁叠加的极端边界Bug。日常开发、常规压测完全无法复现,只有网络异常、命令写入失败、连接归还重试链路才会触发,隐蔽性极强。
自己手写自旋锁一定要考虑线程重入场景,无脑AtomicBoolean自旋锁存在巨大隐患
Netty EventLoop是单线程模型,嵌套回调极易触发方法重入,锁选型必须谨慎
生产中间件不要盲目追新版本,但是要提前熟知已知高危Bug,提前做本地补丁
CPU莫名100%无业务死循环,优先排查Netty线程、中间件客户端底层死循环
如果你们项目使用中等版本Redisson、不敢升级依赖,直接打这个局部源码补丁即可,改动极小、风险为零,彻底规避该隐秘CPU死循环故障。
很多线上疑难告警,从来都不是业务代码的锅,而是第三方组件深藏多年、极低概率触发的底层Bug。
只会写业务代码、不会看线程堆栈、不会读开源源码、不会做局部源码热修复,遇到这类诡异故障只能盲目重启服务。
读懂中间件底层线程模型、看懂开源官方PR修复思路,才是高级工程师和CRUD业务开发的核心分水岭。