RocketMQ消费失败消息深入分析(consumer,broker的具体处理逻辑)

前言

消息队列是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。由于每个消息队列都有它的优势和劣势,我们公司对于不同的场景使用了不同类型的消息队列。对于RocketMQ消费端存在消息消费失败的情况,通常有两种方式,一种是consumer端知道怎么处理,另一种是consumer不能处理(broker处理),本文对后一种情况进行介绍,consumer获取到消息但不能正常处理(ack),接下来这个消费失败的消息在Broker里面如何存储和重新让consumer消费,针对这个流程做了深入的分析。本文中的P代表producer,C代表consumer,本文的consumeQueue对应前面的topic下面的队列。

目录

  • RocketMQ的消费与存储结构
  • RocketMQ的消费失败消息处理逻辑
  • Broker端处理失败消息任务的启动
  • Consumer发回消费失败消息流程
  • Broker写发回失败消息的流程

RocketMQ的消费与存储结构

正常情况下,P发送消息到broker,消息内容写到commitlog,消息内容在commitlog的位置信息(索引)写到consumerQueue,C读取consumerQueue的内容消费消息。


发送消费.png

RocketMq的存储结构:


存储结构.png

本文的内容涉及上面的消费队列服务(consumerQueue,%RETRY%groupName属于consumerQueue),定时消息服务(SCHEDULE_TOPIC_XXXX)两个模块,C与broker的的消息消费只涉及到consumerQueue,定时消息服务只在broker内部起作用。

RocketMQ的消费失败消息处理逻辑

consumer消费失败消息处理流程图如下:


rocketmq消费失败发回broker.png

在下面的代码和流程分析中请结合这个图进行分析。
其中SCHEDULE_TOPIC_XXXX和%RETRY%groupName的queue都存储在目录 ~/store/consumequeue 里面:
ll ~/store/consumequeue 如下:


schedule and retry store.png

ll ~/store/consumequeue/SCHEDULE_TOPIC_XXXX 如下:


schedule queue.png

从上图可以看出SCHEDULE_TOPIC_XXXX的队列名称是从2开始到17,对应的delayLevel为3到18,3对应10s,18对应2h,在类MessageStoreConfig中这样定义延时时间:String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"。SCHEDULE_TOPIC_XXXX这个topic只对内部使用,对于consumer只能消费到retry队列的数据。
consumer消费失败的消息发回broker后总是先写到SCHEDULE_TOPIC_XXXX里面,然后schedule service读取SCHEDULE_TOPIC_XXXX里面的数据写到retry队列,consumer消费retry队列的数据,这样就完成了一个循环,从这个过程也能看到,一个消费失败的消息体每次发回broker需要在commitLog里面存储两份(topic为SCHEDULE_TOPIC_XXXX的一份这个主要是为schedule service控制延时用的,topic为%RETRY%groupName的一份)。
当我们想查看现在的延时消息数量,我们可以查看SCHEDULE_TOPIC_XXXX的offset来得知,使用CLI Admin Tool工具输入命令“sh mqadmin brokerStatus”查看处理进度。如下图:


shcedule offset.png

其中每行为一个队列,图中第一列为队列的名称,图中第二列参数为当前队列处理的offset,图中第三列为当前队列最大存储的offset。通过第三列和第二列的值相减能得出当前的队列的消息数量。

Broker端处理失败消息任务的启动

scheduleMessage类图.png

ScheduleMessageService根据messageDelayLevel维护了每个延迟level对应的队列编号,以及每个队列编号对应的offset。在start方法里面会启动18个timerTask(DeliverDelayedMessageTimerTask),每个对应一个level,初始offset为0。然后就是定时任务读取SCHEDULE_TOPIC_XXXX队列里面的消息进行判断,如果消息的delayLevel对应的时间满足重新消费,那么就会忘consumeQueue里面写这个消息,等待C重新来消费。

Consumer发回消费失败消息流程

consumer发送消费失败消息.png

在ConsumeRequest的run方法里面也就是业务端处理消息的线程里面,对于status是非success的交给ConsumeMessageConcurrentlyService(本文只讨论并行消费的模式,串行模式类似)的sendMessageBack方法处理,这个方法主要设置delayLevel(context.getDelayLevelWhenNextConsume()),然后传递给DefaultMQPushConsumerImpl.sendMessageBack找到对应的消息来源queue,把这个消息发送到这个queue里面,也就是说消费失败的消息发回broker还是会在之前的那个queue里面。发回broker后本地再过5秒重试消费一次,如果这次成功,下次就不再消费。
上面流程的类图:


consusmer发回broker失败消息类图.png

在ConsumeRequest的run方法里面会调用ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this)来处理消费结果状态,在cluster(集群模式)下设置新的消息delayLevle值然后把失败的消息发回Broker,广播模式不发回。注意ConsumeConcurrentlyContext的delayLevelWhenNextConsume属性说明-1直接放到死信队列,0又broker每次对重试消费次数加1来控制重试策略,大于0由consumer控制重试消费策略(在listener的consumeMessage方法里面有个context:context.setDelayLevelWhenNextConsume(4)设置为1分钟延时消费),默认值为0。

Broker写发回失败消息的流程

broker写消费失败的消息.png

broker端收到消费失败消息后通过consumerSendMsgBack(P发送的消息不由这个处理,区分通过消息头的type)方法设置当前消息的delayTimeLevel,这里计算delayTimeLevel,第一次重试默认consumer发回为0,延迟为延迟等级为0+3=3;如果第一次不为0表明是consumer控制的情况,直接取出delayTimeLevel,也就是和ConsumeConcurrentlyContext(consumer端控制)的delayLevelWhenNextConsume配置一致。设置好delayLevelTime后就交给DefaultMessageStore的putMessage方法,DefaultMessageStore的putMessage方法通过Commitlog的putMessage来写入文件,这里需要重点关注的是在这个方法里面通过msg.getDelayTimeLevel() > 0这个条件,修改当前消息topic为SCHEDULE_TOPIC_XXXX,原来的topic保留在property里面,在ScheduleMessageService里面判断消息满足条件后会把消息的topic改为真实的topic,通常是retry,接着写到consumeQueue里面,C对于%RETRY%consumerGroup这个topic在程序里面默认是订阅的不需用户指定,然后队列Id的计算方式为queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()),即msg.getDelayTimeLevel()-1,和前面的截图2到17编号一致,然后消息体写到commitlog文件和索引写到SCHEDULE_TOPIC_XXXX队列。类图如下:


broker处理发回失败消息类图.png

SendMessageProcessor处理远程发来的消息,包括P和C的,方法里面通过RequestCode.CONSUMER_SEND_MSG_BACK来判断是不是重试发回的消息。然后会判断这个消息对应的topic为%RETRY%consumerGroup的是否创建过,没有则创建;接下来的处理就和上面的流程图一样了。

总结

本文围绕consumer端消费失败后RocketMQ各个模块的处理逻辑进行了源码的深入分析。相信有了以上的知识学习和实践之后,当业务应用遇到了类似的问题就可以胸有成竹的应对了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容