正文
1. 故障现象
2020-11-18 10:40开始,业务线反馈线上收到大量的重复MQ半事务消息,导致容器资源消耗急剧攀升,经查看MQ日志,发现broker-b的Master服务,报出大量半事务消息回查日志,且每次回查的起始offset不变化,但opOffset不断迅速增大,且HALF_TOPIC队列急速膨胀,查看RocketMQ console监控web后台,发现出现大量消息堆积,且都在broker-B。offset日志如下:
2. 原因分析
2.1. MQ半事务消息回查机制
1、producer提交半事务消息,会先存储在RMQ_SYS_TRANS_HALF_TOPIC队列(以下简称HALF队列)
2、如果producer在MQ回查前,主动确认了本次事务结果,不管是提交还是回滚,MQ都会把该消息转存至RMQ_SYS_TRANS_OP_HALF_TOPIC队列(以下简称OP队列),且如果事务是成功提交的,同时把消息转存至真实的topic,让消费者进行消费。
3、如果producer未能及时确认事务结果,则MQ会定时消费HALF队列,回查对应事务的结果,根据回查结果进行跟上述第二点一样的处理。
4、如果当前事务消息尚未超时,则本次回查终止。
5、更新HALF队列的消费进度offset,等待下一次定时回查,从最新进度offset开始。
2.2. 故障原因
1、MQ默认对超出4K的消息进行压缩存储,并设置sysFlag为已压缩
2、半事务消息需要回查时,从HALF获取消息,如果消息是被压缩过的,此处会进行解压处理
3、把解压后的消息renew一份,追加到HALF队列后,sysFlag标记仍为已压缩,但实际msgBody已经解压
4、下次回查时,该事务消息仍未有结果,对其进行回查,但将其从HALF里读出时出现异常,因为根据sysFlag,消息进行了压缩,但实际进行解压又失败
5、NPE异常未被捕获,直接抛到TransactionalMessageServiceImpl.check()方法,该方法仅打印出来,未作其他业务处理,导致未能更新HALF的回查offset,导致下次回查,仍然从上次的offset开始,陷入死循环
3. 解决方案
1、临时解决方案:
- a)故障当天通过手动修改HALF的offset,让其跳过异常消息,得以恢复。
- b)可以暂时修改transactionTimeout参数,加大半事务消息的事务超时时间,降低MQ回查的概率,规避出现故障。
- c)可以暂时修改Producer端的compressMsgBodyOverHowmuch参数,加大启用压缩的阈值(不推荐)。
2、最终解决方案:
MQ官方4.6.0+以后的版本已经修复该问题,对新版MQ进行稳定性测试后,升级到生产。
如果你觉得本文对你有点帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?