消费者从Broker拉取到消息之后,会将消息提交到线程池中进行消费,RocketMQ消息消费是批量进行的,如果一批消息的个数小于预先设置的批量消费大小,直接构建消费请求ConsumeRequest将消费请求提交到线程池处理,否则需要分批构建进行提交。

消息消费

在消息被提交到线程池后进行处理时,会调用消息监听器的consumeMessage进行消息消费,它返回消息的消费结果状态,状态有两种分别为CONSUME_SUCCESSRECONSUME_LATER

  • CONSUME_SUCCESS:表示消息消费成功。
  • RECONSUME_LATER:表示消费失败,稍后延迟重新进行消费。

处理消息消费结果

设置ackIndex

在消息消费完毕之后,会根据consumeMessage方法返回的结果状态进行处理,对ackIndex的值进行设置,ackIndex的值用于在下一步中处理消费失败的消息。

前面可知消费结果状态有以下两种:

  • CONSUME_SUCCESS:消息消费成功,此时ackIndex设置为消费的总消息个数 - 1,表示消息都消费成功。
  • RECONSUME_LATER:消息消费失败,延迟进行消费,此时ackIndex值为-1。

二、处理消费失败的消息

广播模式

广播模式下,如果消息消费失败,只将失败的消息打印出来不做其他处理。

集群模式

开启for循环,初始值为i = ackIndex + 1,结束条件为i < consumeRequest.getMsgs().size(),上面可知ackIndex有两种情况:

消费成功:ackIndex值为消息大小-1,此时ackIndex + 1的值等于消息的个数大小,不满足for循环的执行条件,相当于消息都消费成功,不需要进行失败的消息处理。

延迟消费:ackIndex值为-1,此时ackIndex+1为0,满足for循环的执行条件,从第一条消息开始遍历到最后一条消息,向Broker发送CONSUMER_SEND_MSG_BACK请求,如果发送成功Broker会根据延迟等级,放入不同的延迟队列中,到达延迟时间后,消费者将会重新进行拉取,如果发送失败,消费次数加1,并加入到失败消息列表中,稍后重新提交到消息消费线程池进行处理。

发送CONSUMER_SEND_MSG_BACK请求

延迟级别

RocketMQ的延迟级别对应的延迟时间常量定义如下:

public class MessageStoreConfig {
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
}

延迟级别与延迟时间对应关系:

延迟级别0 ---> 对应延迟时间1s,也就是延迟1秒后消费者重新从Broker拉取进行消费

延迟级别1 ---> 延迟时间5s

延迟级别2 ---> 延迟时间10s

...

以此类推,最大的延迟时间为2h。

在向Broker发送CONSUMER_SEND_MSG_BACK请求的时候,会从上下文中获取设置的延迟级别(默认为0,也就是延迟1s),然后设置以下信息,向Broker发送请求:

  • 设置请求类型,请求类型为CONSUMER_SEND_MSG_BACK
  • 设置消费者组名称;
  • 设置消息在CommitLog中的偏移量;
  • 设置延迟级别;
  • 设置消息的ID;
  • 设置该消息的最大消费次数;

Broker对CONSUMER_SEND_MSG_BACK请求处理

Broker对CONSUMER_SEND_MSG_BACK类型的请求处理逻辑如下:

  1. 根据消费组获取该消费者组的订阅信息配置;
  2. 根据消费者组名称获取对应的重试主题;
  3. 从该消费者组的重试队列中随机选取一个队列;
  4. 根据消息在CommitLog中的偏移量从commitLog文件中获取消息内容;
  5. 判断消息的消费次数是否大于等于最大消费次数 或者 延迟等级小于0:
    • 如果条件满足,表示需要把消息放入到死信队列DLQ中,此时从死信队列中随机选取一个队列;
    • 如果条件不满足,判断延迟级别是否为0,如果为0的话,会使用消息的消费次数作 + 3为新的延迟级别进行延迟消费;
  6. 新建消息对象MessageExtBrokerInner,设置消息的相关信息,此时相当于生成了一个全新的消息(会设置之前消息的ID),重新添加到CommitLog中,消息主题的设置有两种情况:
    • 达到了加入DLQ队列的条件,此时主题为DLQ主题(%DLQ% + 消费组名称),消息之后会添加到选取的DLQ队列中;
    • 未达到DLQ队列的条件,设置延迟级别,使用重试主题(%RETRY% + 消费组名称),之后将消息投递到此主题下的队列中;
  7. 调用asyncPutMessage存储消息;

asyncPutMessage方法中,会对延迟级别进行判断,如果延迟时间级别大于0,说明消息需要延迟消费,此时做如下处理:

  1. 获取延迟消息的主题名称,RocketMQ对延迟消息有一个默认的主题名称SCHEDULE_TOPIC_XXXX;
  2. 根据消息设置的延迟级别,获取对应的延迟队列,SCHEDULE_TOPIC_XXXX主题下,会根据延迟级别创建对应的消息队列,所以这一步会根据消息的延迟级别投递到对应的队列中;
  3. 在消息属性中,设置消息原本的主题名称和消息队列,然后将消息当前的Topic改成RMQ_SYS_SCHEDULE_TOPIC

总结

消费者在消息消费失败的时候,会向Broker发送CONSUMER_SEND_MSG_BACK请求,在请求处理中会判断消息的消费次数是否大于最大的消费次数,如果超过最大消费次数,会将消息投递到死信队列中。

如果未达到最大的消费次数,会根据请求中设置的延迟级别,重新生成一条消息,使用重试主题(%RETRY% + 消费组名称),并随机选取一个队列投递消息,延迟进行消费,不过消息不会立刻投递到队列中,在消息存储之前会对延迟级别进行判断,如果需要延迟消费,会使用RocketMQ默认创建的SCHEDULE_TOPIC_XXXX主题,先根据延迟级别将消息投递到对应的延迟队列中,然后由一个定时任务去检测这个主题下的消息,当消息到达延迟的时间后,再将消息取出投递到原本主题下的消息队列中,之后的流程就与普通消息的存储一致,将消息存入CommitLog中,再创建对应的ConsumeQueue数据,消费者就可以拉取到消息重新进行消费。

消费者在启动的时候,会处理订阅的Topic数据,如果是集群模式,会自动添加重试主题的订阅(%RETRY% + 消费组名称),然后就可以从重试主题中拉取到对应的重试消息进行消费。

更新拉取偏移量

以上步骤处理完毕后,首先调用removeMessage从处理队列中移除消息并返回拉取消息的偏移量,然后更新拉取偏移量。

RocketMQ消费模式分为广播模式和集群模式,广播模式下消费进度保存在每个消费者端,集群模式下消费进度保存在Broker端。

广播模式

广播模式对应的OffSetStore实现类为LocalFileOffsetStore,使用了一个ConcurrentMap类型的变量offsetTable存储每个消息队列对应的拉取偏移量,KEY为消息队列,value为该消息队列对应的拉取偏移量。

在更新拉取进度的时候,对offsetTable中的值进行更新,需要注意这里只是更新了offsetTable中的数据,并没有持久化到磁盘。

集群模式

集群模式对应的实现类为RemoteBrokerOffsetStore,更新进度与广播模式下的更新类似,都是只更新了offsetTable中的数据。

持久化的触发

消费者在启动的时候注册了定时任务,定时将消息拉取进度进行持久化,对于广播模式,将每个消息队列对应的拉取偏移量持久化到本地文件即可,对于集群模式,由于拉取进度保存在Broker端,所以需要向Broker发送请求进行持久化。

RocketMQ消息的消费相关源码可参考:【RocketMQ】【源码】消息的消费

【RocketMQ】消息的消费总结的更多相关文章

  1. 程序重启RocketMQ消息重复消费

    最近在调试RocketMQ消息发送与消费的Demo时,发现一个问题:只要重启程序,RocketMQ消息就会重复消费. 那么这是什么原因导致的,又该如何解决呢? 经过一番排查,发现程序使用的Rocket ...

  2. 关于RocketMQ消息消费与重平衡的一些问题探讨

    其实最好的学习方式就是互相交流,最近也有跟网友讨论了一些关于 RocketMQ 消息拉取与重平衡的问题,我姑且在这里写下我的一些总结. ## 关于 push 模式下的消息循环拉取问题 之前发表了一篇关 ...

  3. rocketMq消息的发送和消息消费

    rocketMq消息的发送和消息消费 一.消息推送 public void pushMessage() { String message = "推送消息内容!"; try { De ...

  4. RocketMQ(消息重发、重复消费、事务、消息模式)

    分布式开放消息系统(RocketMQ)的原理与实践 RocketMQ基础:https://github.com/apache/rocketmq/tree/rocketmq-all-4.5.1/docs ...

  5. 【RocketMQ】消息的消费

    上一讲[RocketMQ]消息的拉取 消息消费 当RocketMQ进行消息消费的时候,是通过ConsumeMessageConcurrentlyService的submitConsumeRequest ...

  6. 太坑了,我竟然从RocketMQ源码中扒出了7种导致消息重复消费的原因

    大家好,我是三友~~ 在众多关于MQ的面试八股文中有这么一道题,"如何保证MQ消息消费的幂等性". 为什么需要保证幂等性呢?是因为消息会重复消费. 为什么消息会重复消费? 明明已经 ...

  7. RocketMQ 消息消费

    消息消费 难点:如何保证消息只消费一次? 消费模式: 1.单一消费模式:一条消息,仅被一个消费者进行消费. 如何进行负载?负载算法有 a.平均分配.b.平均轮询分配.c.一致性hash(不推荐).d. ...

  8. RocketMq消息队列使用

    最近在看消息队列框架 ,alibaba的RocketMQ单机支持1万以上的持久化队列,支持诸多特性, 目前RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,bin ...

  9. RocketMQ源码 — 八、 RocketMQ消息重试

    RocketMQ的消息重试包含了producer发送消息的重试和consumer消息消费的重试. producer发送消息重试 producer在发送消息的时候如果发送失败了,RocketMQ会自动重 ...

  10. RocketMQ 消息队列单机部署及使用

    转载请注明来源:http://blog.csdn.net/loongshawn/article/details/51086876 相关文章: <RocketMQ 消息队列单机部署及使用> ...

随机推荐

  1. GO 集合 map 使用总结

    转载请注明出处: Go语言的集合称为映射(map),它是一种无序的键值对(key-value)的集合,集合是通过键(key)来快速检索值(value)的,键(key)类似于索引,它指向值(value) ...

  2. SpringBoot之MVC配置(WebMvcConfigurer详解)

    一:基本介绍 Spring MVC是一种常用的Java Web框架,它提供了一种基于MVC模式的开发方式,可以方便地实现Web应用程序.在Spring MVC中,WebMvcConfigurer是一种 ...

  3. RLHF技术在智能娱乐中的应用:提高娱乐智能化和自动化水平”

    目录 随着人工智能技术的不断发展,智能娱乐成为了人们越来越关注的话题.在智能娱乐领域中,RLHF技术的应用正在逐渐显现.本文将介绍RLHF技术在智能娱乐中的应用,提高娱乐智能化和自动化水平. 首先,我 ...

  4. Python潮流周刊#8:Python 3.13 计划将解释器提速 50%!

    你好,我是猫哥.这里每周分享优质的 Python 及通用技术内容,部分为英文,已在小标题注明.(标题取自其中一则分享,不代表全部内容都是该主题,特此声明.) 首发于我的博客:https://pytho ...

  5. Oracle将用户权限移植到另一个用户上

    问题描述:往往有些需求,A用户依赖于B用户创建,A用户想要获取B用户的权限,oracle没找到有命令可以直接继承,只能写一些语句来代替 1.查询用户下的权限有哪些 SET PAGESIZE 100 S ...

  6. 开发中MongoDB遇到的各种问题

    目录 一.安装6版本以下 二.安装6版本及以上 三.安装6版本以下(解压版) 四.配置本地 Windows MongoGB 服务 五.navicat 连接远程mongodb数据库 六.ip不一致问题 ...

  7. Day03_Java_作业

    A:选择题 1.给出以下代码,请问表达式grade==70在以下两条语句中是否获得执行? boolean flag = false && grade == 70;//第一条语句 boo ...

  8. 【环境搭建】vscode调试php

    待解决问题 使用vscode和phpstudy实现PHP的本地调试 解决办法 1.打开xdebug 找到网站使用的PHP版本,在设置中将Xdebug调试组件打开,并确认端口是9000 找到php扩展目 ...

  9. struct 结构体分析

    struct分析 1.无成员的空结构体size为 1byte 2.通过/zp可以调整对齐值,默认是8字节 //设编译对齐设定值为Zp //设成员变量的类型为 member type //设成员变量在结 ...

  10. PerfView 洞察C#托管堆内存 "黑洞现象"

    一:背景 1. 讲故事 首先声明的是这个 黑洞 是我定义的术语,它是用来表示 内存吞噬 的一种现象,何为 内存吞噬,我们来看一张图. 从上面的 卦象图 来看,GCHeap 的 Allocated=85 ...