实战:RocketMQ消息丢失如何排查?

3552 篇文章 115 订阅

消息丢失如何排查?

当我们在使用mq的时候,经常会遇到消息消费异常的问题,原因有很多种,比如

  1. producer发送失败

  2. consumer消费异常

  3. consumer根本就没收到消息

「那么我们该如何排查了?」

其实借助RocketMQ-Dashboard就能高效的排查,里面有很多你想象不到的功能

首先我们先查找期望消费的消息,查找的方式有很多种,根据消息id,时间等

「消息没找到?」

说明proder发送异常,也有可能是消息过期了,因为rocketmq的消息默认保存72h,此时到producer端的日志进一步确认即可

「消息找到了!」

接着看消息的消费状态,如下图消息的消费状态为NOT_ONLINE

「NOT_ONLINE代表什么含义呢?」

别着急,我们一步步来分析,先看看TrackType到底有多少种状态

public enum TrackType {
    CONSUMED,
    CONSUMED_BUT_FILTERED,
    PULL,
    NOT_CONSUME_YET,
    NOT_ONLINE,
    UNKNOWN
}

每种类型的解释如下

类型解释
CONSUMED消息已经被消费
CONSUMED_BUT_FILTERED消息已经投递但被过滤
PULL消息消费的方式是拉模式
NOT_CONSUME_YET目前没有被消费
NOT_ONLINECONSUMER不在线
UNKNOWN未知错误

「怎么判定消息已经被消费?」

上一节我们讲到,broker会用一个map来保存每个queue的消费进度,「如果queue的offset大于被查询消息的offset则消息被消费,否则没有被消费」(NOT_CONSUME_YET)

我们在RocketMQ-Dashboard上其实就能看到每个队列broker端的offset(代理者位点)以及消息消费的offset(消费者位点),差值就是没有被消费的消息

当消息都被消费时,差值为0,如下图所示

「CONSUMED_BUT_FILTERED表示消息已经投递,但是已经被过滤掉了」。例如producer发的是topicA,tagA,但是consumer订阅的却是topicA,tagB

「CONSUMED_BUT_FILTERED(消息已经被投递但被过滤)是怎么发生的呢?」

这个就不得不提到RocketMQ中的一个概念,「消息消费要满足订阅关系一致性,即一个consumerGroup中的所有消费者订阅的topic和tag必须保持一致,不然就会造成消息丢失」

如下图场景,发送了4条消息,consumer1订阅了topica-taga,而consumer2订阅了topica-tab。consumer1消费q0中的数据,consumer2消费q1中的数据

投递到q0的msg-1和msg-3只有msg-1能被正常消费,而msg-3则是CONSUMED_BUT_FILTERED。因为msg-3被投递到q0,但是consumer1不消费tagb的消息导致消息被过滤,造成消息丢失

同理msg-2这条消息也会丢失

「注意,还有一个非常重要的点」

虽然消息消费失败了,但是消息的offset还会正常提交,即 「消息消费失败了,但是状态也会是CONSUMED」

「RocketMQ认为消息消费失败需要重试的场景有哪些?」

  1. 返回ConsumeConcurrentlyStatus.RECONSUME_LATER

  2. 返回null

  3. 主动或被动抛出异常

「那么消费失败的消息去哪了呢?」

当消息消费失败,会被放到重试队列中,Topic名字为%RETRY% + consumerGroup

「Consumer没订阅这个topic啊,怎么才能消费到重试消息?」

其实在Consumer启动的时候,框架内部帮你订阅了这个topic,所以重试消息能被消费到

「另外消息不是一直重试,而是每隔1段时间进行重试」

第几次重试与上次重试的间隔时间第几次重试与上次重试的间隔时间
110 秒97 分钟
230 秒108 分钟
31 分钟119 分钟
42 分钟1210 分钟
53 分钟1320 分钟
64 分钟1430 分钟
75 分钟151 小时
86 分钟162 小时

当消息超过最大消费次数16次,会将消息投递到死信队列中,死信队列的topic名为%DLQ% + consumerGroup。

「因此当你发现消息状态为CONSUMED,但是消费失败时,去重试队列和死信队列中找就行了」

消息消费异常排查实战

这个问题发生的背景是这样的,就是我们有2个系统,中间通过mq来保证数据的一致性,结果有一天数据不一致了,那肯定是consumer消费消息有问题,或者producer发送消息有问题

先根据时间段找到了消息,确保了发送没有问题,接着看消息的状态为NOT_CONSUME_YET,说明consumer在线但是没有消息

「NOT_CONSUME_YET表明消息没有被消费」,但是消息发送都过了好长时间了,consumer不应该没消费啊,查日志consumer确实没有消费。

用RocketMQ-Dashboard查看一下代理者位点和消费者位点,0队列正常消费,其他队列没有被消费

「感觉这个负载均衡策略有点问题啊,怎么0队列这么多消息,别的队列都怎么没消息,问一波中间件的同学,是不是又改负载均衡策略了?」

确实改了!测试环境下,采用队列纬度区分多环境,0是基准环境,我们团队目前还没有用多环境,所以收发消息都会在队列0上,其他队列不会用到(「你可以简单认为测试环境发送和消费消息只会用到0队列」

「那么问题来了!」

首先消息的状态是NOT_CONSUME_YET,说明消息肯定被投递到0队列之外了,但是中间件的小伙伴却说消息不会被投递到0队列

要想验证我的想法首先需要证明没有被消费的消息确实被投递到0队列之外的队列了

中间走的弯路就不说了,直到我看了看RocketMQ-Dashboard的源码,「发现Dashboard其实返回了消息的很多信息,但是并没有在页面展示出来,直接看接口返回」

乖乖,发现了新世界,消息的所有属性都在这了,看到queuId为14,果然验证了我的想法。

再看bornHost居然是我们办公室的网段

「难道本地启动的负载均衡策略和测试环境的负载均衡策略不一样?」

本地debug一波代码,果然是本地的producer会往所有的队列发送消息,并且consumer也会消费所有队列的消息

「至此找出问题了!」

producer在本地启了一个服务,注册到测试环境的zk,测试环境的部分请求打到本地,往0队列之外的队列发了消息,但是测试环境的consumer只会消费0队列中的消息,导致消息迟迟没有被消费。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值