问题:生产环境 rabbitmq 部分客户端 channel 持续积压消息不进行ack。
0. 服务配置rabbitmq 集群(普通集群模式)消费者 三台 消费线程各消费者 10消费者配置 使用 spring-amqp|auto-ack 模式1. 故障发现
近日有同学发现一个业务队列存在上千个 unacked 消息,并且有持续上涨的趋势。
2. 故障表现
队列下其中两个客户端的各一个 channel 分别阻塞几百条数据,并且在持续累加,重启应用后队列 unacked 消息全部进入 ready 状态等待重消费,但是重启后客户端依然有 channel 重新开始堆积并且在趋势上涨。
3. 问题排查
排查思路
检查 mq 控制台是否是队列创建问题消费者阻塞是否有规律可循(未ack数据是否有共同特征、阻塞客户端配置是否有问题)客户端代码是否有问题、应用是否有jvm级别故障4. 问题定位
经过一番筛查,问题定位到了代码部分,队列消费代码并非刚上线,而是在前一日服务重启后出现的这个问题,重新 review 代码后发现消费者有使用 CountDownLatch 等待多线程消费结果,CountDownLatch#countDown的调用没有放到 finally 中执行,并且提交到线程池的任务也没有使用 try catch 进行包裹。
到此怀疑是消费线程阻塞到了 CountDownLatch#await 处,异步任务处理时由于偶现异常代码并未执行到 CountDownLatch#countDown 处,再者由于异步任务未捕获异常导致错误直接抛到 jvm 日志无法记录错误。
为了验证这个问题,我们又dump了阻塞服务的栈信息,发现确实有消费者线程阻塞到 CountDownLatch#await 处,问题定位结束。
5. 解决方案
从任务处下手添加 catch 记录日志,并将 CountDownLatch#await 放到 finally 中执行。重启应用再次观察,并未出现 unacked 消息,观察日志也并未出现新添加的 error 日志。
6.问题拓展
同一个 channel 为何会阻塞那么多数据?
线上生产环境采用推模式,rabbitmq 通过 channel 推送消息到客户端,客户端采用 LinkedBlockingQueue 做缓存,一个 channel 对应一个消费者线程,当消费者线程阻塞时 LinkedBlockingQueue 作为中转一直在预存消息,所以会出现很多 unacked 消息。
为什么仅有部分一两个 channel 出现堆积?
线上添加错误日志后实际并未出现错误打印,怀疑之前异常可能是由于重启后第一次请求 rpc 偶现调用失败,猜测暂无法复现,后续需观察日志。
总结谨慎使用线程同步,谨防线程死锁,务必保证线程不会 hang死。自建线程池做好错误兜底,不要将异常抛给jvm。