基础架构
Apache RocketMQ 是一个分布式消息和流媒体平台,它由四个部分组成:名称服务器、代理、生产者和消费者。它们中的每一个都可以水平扩展以杜绝单点故障,础架构图如下:
NameServer
相当于一个注册中心作用同zk类似,提供轻量级服务发现和路由。为确保集群在一个实例崩溃时仍能正常运行,建议使用两个或多个名称服务器实例,只要有一个名称服务器实例处于活动状态,整个集群就会保持服务状态。注意:NameServer 之间不会互相通信,每个Name Server都会记录完整的路由信息,相当于一个多主的状态,有两个主要作用:
- Broker 管理:每个 Broker 在启动的时候会到 NameServer 中注册,NameServer 与每台 Broker 服务保持长连接,并每间隔10s检查一次Broker存活状态,如果NameServer超过2分钟没有收到Broker心跳信息,则NameServer会断开与Broker的连接,然后从路由注册表中将其移除;
- 路由管理:Producer在发送消息前会根据Topic到NameServer获取到Broker的路由信息,进而和Broker取得连接,Consumer也会定时获取Topic的路由信息,有通过下列四种方式获取路由信息,优先级从高到低:
编码方式:producer.setNamesrvAddr("ip:port").
配置方式:使用rocketmq.namesrv.addr.
环境变量:使用NAMESRV_ADDR.
http方式:http://jmenv.tbsite.net:8080/rocketmq/nsaddr(推荐)
Broker
Broker 服务器负责消息的存储和传递、消息查询、HA 保证,每个功能都由一个单独的模块实现,在RocketMQ中简化了NameServer功能,转而全部由Broker来承担。具体的核心模块如下:
- Remoting Service:处理来自客户端的请求,管理生产者/消费者并维护消费者的主题订阅。
- Store Service:提供简单的 API 来在物理磁盘中存储或查询消息。
- HA Service:提供主代理和从代理之间的数据同步功能。
- Index Service:消息建立索引,并提供快速的消息查询
Broker通过提供轻量级的 Topic 和 Queue 机制来处理消息存储,架构层面通过master-slave方式实现容错机制,master节点主要提供RW访问,slave节点主要提供R功能。每个Broker都有一个唯一ID,ID==0的节点默认为master节点。RocketMQ 存储用的是本地文件存储系统,效率高还算不错。消息存储主要由三部分组成分别是 CommitLog、ConsumeQueue、IndexFile,流程如下:
CommitLog
RocketMQ 的所有主题的消息都存放在 CommitLog 中,消息先写入 CommitLog 再通过后台线程分发到 ConsumerQueue 和 IndexFile 中。CommitLog 采用混合型存储,也就是所有 Topic 都存在一起,顺序追加写入,单个 CommitLog 默认 1G,并且文件名以起始偏移量命名,固定 20 位,不足则前面补 0,比如 00000000000000000000 代表了第一个文件,第二个文件名就是 00000000001073741824,表明起始偏移量为 1073741824,以这样的方式命名用偏移量就能找到对应的文件。所有消息都是顺序写入的,超过文件大小上限时会新建一个文件,这时 RocketMQ 会起一个后台线程 AllocateMappedFileService,不断的处理 AllocateRequest,AllocateRequest 其实就是预分配的请求,会提前准备好下一个文件的分配,防止在消息写入的过程中分配文件,产生抖动。
ConsumeQueue
ConsumeQueue 消息消费队列,存储的是CommitLog中消息的索引(物理地址),因为 CommitLog 是糅合了所有主题的消息,所以通过索引才能更加高效的查找消息(非消费)。ConsumeQueue 存储的条目是固定大小,只会存储 8 字节的 Commitlog 物理偏移量,4 字节的消息长度和 8 字节 Tag 的哈希值,固定 20 字节。在实际存储中,ConsumeQueue 对应的是一个Topic 下的某个 Queue,每个文件约 5.72M,由 30w 条数据组成。消费者是先从 ConsumeQueue 来得到消息真实的物理地址,然后再去 CommitLog 获取消息。所以只要有 CommitLog在,ConsumeQueue即使数据丢失,仍然可以恢复出来。
IndexFile
IndexFile 就是索引文件,是额外提供查找消息的手段,不影响主流程。通过 Key 或者时间区间来查询对应的消息,文件名以创建时间戳命名,固定的单个 IndexFile 文件大小约为400M,一个 IndexFile 存储 2000W个索引。
总结一下就是,消息到了先存储到 Commitlog,然后会有一个 ReputMessageService 线程接近实时地将消息转发给消息消费队列文件与索引文件,这个过程是异步的,也可以通过配置ACK机制修改这个机制。
另外其可用的配置大即如下:
brokerClusterName=DefaultCluster
brokerName=broker-a //默认值null
brokerId=0
deleteWhen=04 //何时删除超过保留时间的提交日志
fileReservedTime=48 //在删除之前保留提交日志的小时数,默认72小时
brokerRole=ASYNC_MASTER //可选值SYNC_MASTER/ASYNC_MASTER/SLAVE
flushDiskType=ASYNC_FLUSH //可选值SYNC_FLUSH/ASYNC_FLUSH
storePathRootDir=/data/rocketmq/rootdir-a-m //CommitLog文件位置,默认值 $HOME/store/consumequeue/
storePathCommitLog=/data/rocketmq/commitlog-a-m //ConsumeQueue文件位置, 默认值$HOME/store/commitlog/
autoCreateSubscriptionGroup=true
mapedFileSizeCommitLog= 1024 * 1024 * 1024(1G) //CommitLog文件大小
## if msg tracing is open,the flag will be true
traceTopicEnable=true
listenPort=10911
brokerIP1=XX.XX.XX.XX1
namesrvAddr=XX.XX.XX.XX:9876
Producer
生产者支持分布式部署,通过多种负载均衡方式向 Broker 集群发送消息。在发送消息时,可以通过SendResult的SendStatus来获取发送状态(isWaitStoreMsgOK=true,默认值),否则永远得到的是SEND_OK,全部的状态值解释如下,注意这些和ACK机制有关:
- FLUSH_DISK_TIMEOUT:如果 Broker 设置 MessageStoreConfig 的 FlushDiskType=SYNC_FLUSH(默认为 ASYNC_FLUSH),并且 Broker 没有在 MessageStoreConfig 的 syncFlushTimeout(默认为 5 秒)内完成刷新磁盘,您将获得此状态。
- FLUSH_SLAVE_TIMEOUT:如果 Broker 的角色是 SYNC_MASTER(默认为 ASYNC_MASTER),并且从 Broker 没有在 MessageStoreConfig 的 syncFlushTimeout(默认为 5 秒)内完成与 master 的同步,您将获得此状态。
- SLAVE_NOT_AVAILABLE:如果 Broker 的角色是 SYNC_MASTER(默认为 ASYNC_MASTER),但没有配置从属 Broker,您将获得此状态。
- SEND_OK:SEND_OK 并不意味着它是可靠的。为确保不会丢失任何消息,您还应该启用 SYNC_MASTER 或 SYNC_FLUSH。
重复消息和丢失
这里需要注意一个消息重复或缺失的问题,如果您收到 FLUSH_DISK_TIMEOUT、FLUSH_SLAVE_TIMEOUT 并且 Broker 恰好在此时关闭,您会发现您的消息丢失了。这时候你有两种选择:
- 放手不管:任由这条消息丢失;
- 重新发送消息:这可能会导致消息重复。但是如果返回 SLAVE_NOT_AVAILABLE 时,重新发送是没有用的。如果发生这种情况,您应该保留现场并提醒集群管理员;
ProducerGroup
相同角色(是指Broker的角色)的生产者被分组在一起。broker可能会联系同一生产者组的不同生产者实例以提交或回滚事务,以防原始生产者在事务之后崩溃,相当于一个组中可以有多个实例间接实现了负载均衡功能。它的默认实现类为DefaultMQProducerImpl,这是一个线程安全类,在配置并启动后可在多个线程间共享此对象,主要配置如下:
producerGroup: //Producer组名, 默认值为DEFAULT_PRODUCER
createTopicKey://自动创建测试的topic名称, 默认值为TBW102;
defaultTopicQueueNums://创建默认topic的queue数量。默认4;
sendMsgTimeout://发送消息超时时间,默认值10000,单位毫秒
compressMsgBodyOverHowmuch://消息体压缩阈值,默认为4k
retryTimesWhenSendFailed://同步模式,返回发送消息失败前内部重试发送的最大次数。可能导致消息重复。默认2
retryTimesWhenSendAsyncFailed://异步模式,返回发送消息失败前内部重试发送的最大次数。可能导致消息重复。默认2
retryAnotherBrokerWhenNotStoreOK://声明发送失败时,下次是否投递给其他Broker,默认false
maxMessageSize://最大消息大小。默认4M; 客户端限制的消息大小,超过报错,同时服务端也会限制
traceDispatcher://消息追踪器,定义了异步传输数据接口。使用rcpHook来追踪消息
Consumer
Consumer也支持推拉模型的分布式部署,同时支持集群消费和消息广播(注意:在RocketMQ实现中这两种消息类型和服务端无关,都是在消费端来设置的)。
一个主题一般会有多个ConsumerGroup每个ConsumerGroup都会有自己的消费 offset(消息偏移标识)。同时RocketMQ对于Consumer也提供了ACK机制,以保证消息能够被正常消费,为了保证消息肯定消费成功,只有Consumer明确表示消费成功,RocketMQ才会认为消息消费成功,有两种特殊类型的消息需要关注:
- 顺序消费时:Consumer 会锁定每个 MessageQueue 以确保它被一个一个按顺序消费,这是因为rocketmq的顺序消费不是全局的,只是在同一个queue中是顺序的,其特点是将导致性能损失,在编写程序时如果消费出错不建议抛出异常,可以返回 ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT状态码;
- 并发消费时:并发的数量要小于队列的长度,否则会浪费消费线程,在编写程序时不建议抛出异常,可以返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 代替。对于MessageListenerConcurrently,可以返回RECONSUME_LATER,告诉消费者你现在不能消费,想以后再消费。对于MessageListenerOrderly,因为关心顺序,所以不能跳过消息,但是可以返回SUSPEND_CURRENT_QUEUE_A_MOMENT,告诉消费者稍等片刻。并发消费消息时,消息消费的最大并发仅受为每个消费者客户端指定的线程池限制。
在java中Consumer的默认实现类为DefaultMQPushConsumer,接收Broker推过来的数据,内部实现了流控,消费位置上报等等,它的主要配置如下:
consumerGroup://消费者组名,必须设置,参数默认值DEFAULT_CONSUMER ,如果多个消费者如果具有同样的组名,那么这些消费者必须只消费同一个topic
messageModel://消费的方式,支持以下两种 1、集群消费 2、广播消费。
consumeFromWhere://消费者从那个位置消费,以下所说的第一次启动是指从来没有消费过的消费者,如果该消费者消费过,那么会在broker端记录该消费者的消费位置,如果该消费者挂了再启动,那么自动从上次消费的进度开始,可选配置有:
CONSUME_FROM_LAST_OFFSET: //第一次启动从队列最后位置消费,后续再启动接着上次消费的进度开始消费 ;
CONSUME_FROM_FIRST_OFFSET://第一次启动从队列初始位置消费,后续再启动接着上次消费的进度开始消费;
CONSUME_FROM_TIMESTAMP://第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费;
subscription://topic对应的订阅tag
messageListener://消息监听器 ,处理消息的业务就在监听里面。目前支持的监听模式包括:
MessageListenerConcurrently:正常消息消费,对应的处理逻辑类是MessageListener
MessageListenerOrderly:顺序消息,对应的处理逻辑类是ConsumeMessageOrderlyService;
offsetStore://offset存储实现分为LocalFileOffsetStore 和 RemoteBrokerOffsetStore。若没有显示设置的情况下,广播模式将使用LocalFileOffsetStore,集群模式将使用RemoteBrokerOffsetStore,不建议修改。
运行时的配置主要如下:
int consumeThreadMin = 20 线程池自动调整
int consumeThreadMax = 64 线程池自动调整
long adjustThreadPoolNumsThreshold = 100000
int consumeConcurrentlyMaxSpan = 2000 单队列并行消费最大跨度,用于流控
int pullThresholdForQueue = 1000 一个queue最大消费的消息个数,用于流控
long pullInterval = 0 检查拉取消息的间隔时间,由于是长轮询,所以为 0,但是如果应用为了流控,也可以设置大于 0 的值,单位毫秒,取值范围: [0, 65535]
int consumeMessageBatchMaxSize = 1 并发消费时,一次消费消息的数量,默认为1,假如修改为50,此时若有100条消息,那么会创建两个线程,每个线程分配50条消息,取值范围: [1, 1024]。默认是1
int pullBatchSize = 32 消费者去broker拉取消息时,一次拉取多少条。取值范围: [1, 1024]。默认是32
boolean postSubscriptionWhenPull = false
boolean unitMode = false
Message
数据结构
Message
每个Message都有Topic这一属性,Producer发送指定Topic的消息,Consumer订阅Topic下的消息。通过Topic字段,Producer会获取消息投递的路由信息,决定发送给哪个Broker。消息默认保存3天,超过 3 天未使用的消息将被删除,消息大小一般为 256KB,建议不要超过1M。集群消费模时如果一条消息消费失败,最多重试16次,之后该消息将被丢弃。
RocketMQ通过三种方式发送消息:
- 可靠同步:可靠同步传输应用场景广泛,如重要通知消息、短信通知、短信营销系统等。
- 可靠异步:异步传输一般用于响应时间敏感的业务场景。
- 单向传输:单向传输用于需要中等可靠性的情况,例如日志收集, 这里只有单向发送时才有可能丢失消息,其它的不太会。
- 顺序消息:是指queue中有序,可以通过 MessageQueueSelector 指定 Producer 某个业务只发这一个队列,因为cumser是绑室某个队列上的,所以用MessageListenerOrderly接收就可以实现顺序消费了;
- 延迟消息:在发送端设置时间,延迟等级为3~18。(messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h")
重要属性如下:
flag://网络通信层标记。
body://Producer要发送的实际消息内容,以字节数组形式进行存储。Message消息有一定大小限制。
transactionId://RocketMQ 4.3.0引入的事务消息相关的事务编号。
properties: //该字段为一个HashMap,存储了Message其余各项参数,比如tag、key等关键的消息属性。RocketMQ预定义了一组内置属性,除了内置属性之外,还可以设置任意自定义属性。
关于properties说明,需要注意的是因为整个message大小有限,所以properties的内容也会挤占消息大小空间,所以不要设置过多自定义的参数,java-API为
class Message {
public void setTags(String tags) {...}
public void setKeys(Collection<String> keys) {...}
public void setDelayTimeLevel(int level) {...}
public void setWaitStoreMsgOK(boolean waitStoreMsgOK) {...}
public void setBuyerId(String buyerId) {...}
}
properties是保存在map中,所以mq也预定了相关的key,定义在org.apache.rocketmq.common.message.MessageConst.java类中,预定的key如下
MessageConst.PROPERTY_TAGS setTags 在消费消息时可以通过tag进行消息过滤判定
MessageConst.PROPERTY_KEYS setKeys 可以设置业务相关标识,用于消费处理判定,或消息追踪查询
MessageConst.PROPERTY_DELAY_TIME_LEVEL setDelayTimeLevel 消息延迟处理级别,不同级别对应不同延迟时间
MessageConst.PROPERTY_WAIT_STORE_MSG_OK setWaitStoreMsgOK 在同步刷盘情况下是否需要等待数据落地才认为消息发送成功
MessageExt
对于Producer来说,上述Message的定义基本足够。但对于RocketMQ的整个处理流程来说,可能还需要更多的字段信息用以记录一些必要内容,比如消息的id、创建时间、存储时间等,这时就需要用到MessageExt,这里记录了一些过程结果数据,主要扩展的信息如下:
queueId://记录MessageQueue编号,消息会被发送到Topic下的MessageQueue
storeSize: //记录消息在Broker存盘大小
queueOffset: //记录在ConsumeQueue中的偏移
sysFlag: //记录一些系统标志的开关状态,MessageSysFlag中定义了系统标识
bornTimestamp: //消息创建时间,在Producer发送消息时设置
storeHost: //记录存储该消息的Broker地址
msgId: //消息Id,Broker端进行存储时通过MessageDecoder.createMessageId方法生成,Producer在发送消息时没有该信息,Consumer在消费消息时则能获取到该值,全局唯一
commitLogOffset: //记录在Broker中存储便宜
bodyCRC: //消息内容CRC校验值
reconsumeTimes: //消息重试消费次数
preparedTransactionOffset: //事务详细相关字段
Queue
理解Queue和Tag可以先看下Broker的描述,为了灵活性Topic被划分为一个或多个Tag。比如同一业务模块的不同用途的消息可以具有相同的主题和不同的标签,这时就可以使用Tag功能,Tag有助于保持代码的整洁和连贯,Tag也可以方便 RocketMQ 提供的查询系统。Topic由多个队列组成,队列会平均分散在多个Broker上。Producer的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落在每个Broker上。发送消息时RocketMQ 会轮询该 Topic 下的所有队列将消息发出去。其实Topic只是一个逻辑上的概念,下面的消息队列才是真正的实体。默认里一个topic的queue数量为4;
- msgId:全局唯一
- offsetMsgId:消息偏移ID,该 ID 记录了消息所在集群的物理地址,包含所存储 Broker 服务器的地址( IP 与端口号)以及所在commitlog 文件的物理偏移量
Filter
除了tag外,另外还有一种自定义的消息过滤机制,就是userProperty。简单例子如下:
//producer
Message msg = new Message(MqProducerUtil.topic ,
MqProducerUtil.tagA,
("Hello RocketMQ ").getBytes(RemotingHelper.DEFAULT_CHARSET) );
msg.putUserProperty("a", "5");
SendResult sendResult = producer.send(msg);
//consumer, 这里的a为自定义属性:可用的sql语句如下:
/* 1、数值比较,如>, >=, <, <=, BETWEEN, =;*/
/*2、字符比较,如=, <>, IN;*/
/*3、IS NULL或IS NOT NULL;*/
/*4、逻辑AND, OR, NOT;*/
public static MessageSelector messageSelector = MessageSelector.bySql("a between 0 and 3");
DefaultMQPushConsumer consumerFilter = new DefaultMQPushConsumer("example_group_name");
consumerFilter.subscribe(topic, messageSelector);
服务过程
- NameServer 先启动
- Broker 启动时向 NameServer 注册
- 生产者在发送某个主题的消息之前先从 NamerServer 获取 Broker 服务器地址列表(有可能是集群),然后根据负载均衡算法从列表中选择一台Broker 进行消息发送。
- NameServer 与每台 Broker 服务器保持长连接,并间隔 30s 检测 Broker 是否存活,如果检测到Broker 宕机(使用心跳机制, 如果检测超120S),则从路由注册表中将其移除。
- 消费者在订阅某个主题的消息之前从 NamerServer 获取 Broker 服务器地址列表(有可能是集群),但是消费者选择从 Broker 中 订阅消息,订阅规则由 Broker 配置决定
重要特性
高可用保障
总结来讲,RocketMQ在同步刷盘机制下可以确保不丢失消息,在异步刷盘模式下,会丢失少量消息,(如果引入双写机制,就不会丢消息了)。因为mq的主从采用的是方式异步复制机制,RocketMQ通过消息消费确认机制(ACK)来确保消息至少被消费一次,但由于ACK消息有可能丢失等其他原因,RocketMQ无法做到消息只被消费一次,有重复消费的可能。
它的高可用可分解为以下三方面。
Broker
每个Broker与NameServer集群中的所有节点建立长连接,定时(每隔30s)注册Topic信息到所有NameServer。NameServer定时(每隔10s)扫描所有存活broker的连接,如果Name Server超过2分钟没有收到心跳,则Name Server断开与Broker的连接。这时只能消费消息但不能发送消息。
另外正常情况下只有当前客户端读的 offset 和 当前 Broker 已接受的最大 offset 超过限制的物理内存大小时候才会去从读,所以正常情况下从分担不了流量。
Producer
Producer每隔30s(由ClientConfig的pollNameServerInterval)在发送消息之前先从Nameserver获取所有Topic队列的最新情况,这意味着如果Broker不可用,Producer最多30s能够感知,在此期间内发往Broker的所有消息都会失败。Producer每隔30s(由ClientConfig中heartbeatBrokerInterval决定)向所有关联的broker发送心跳,Broker每隔10s中扫描所有存活的连接,如果Broker在2分钟内没有收到心跳数据,则关闭与Producer的连接。
Consumer
Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。当Consumer得到master宕机通知后,转向slave消费,slave不能保证master的消息100%都同步过来了,因此会有少量的消息丢失。但是一旦master恢复,未同步过去的消息会被最终消费掉。
- DefaultPushConsumer: Broker收到新消息请求后,如果队列里没有新消息,并不急于返回,通过一个循环不断查看状态,每次waitForRunning一段时间(5s),然后在check。当一直没有新消息,第三次check时,等待时间超过suspendMaxTimeMills(15s),就返回空结果。在等待的过程中,Broker收到了新的消息后会直接调用notifyMessageArriving返回请求结果。“长轮询”的核心是,Broker端Hold住(挂起)客户端客户端过来的请求一小段时间,在这个时间内有新消息到达,就利用现有的连接立刻返回消息给Consumer。“长轮询”的主动权还是掌握在Consumer手中,Broker即使有大量消息积压,也不会主动推送给Consumer。长轮询方式的局限性,是在Hold住Consumer请求的时候需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中。
- DefaultPullConsumer: 需要用户自己处理遍历MessageQueue、保存Offset,所以PullConsumer有更多的自主性和灵活性。
基于以上消息理论上不会丢失,如果丢失会有两种情况:1、消息发送后未落盘前断电了,Producer又没有重发;2、Master一直没再启动。所以消息会不会丢失是一个相对值,受部署方式和ACK机制影响。
高性能保证
RocketMQ的高性能在于顺序写盘(CommitLog)、零拷贝和跳跃读(尽量命中PageCache),高可靠性在于刷盘和Master/Slave。发送消息负载均衡,且发送消息线程安全(可满足多个实例死循环发消息),集群消费模式下消费者端负载均衡,这些特性加上上述的高性能读写, 共同造就了RocketMQ的高并发读写能力。RocketMQ具有很好动态伸缩能力(非顺序消息),伸缩性体现在Topic和Broker两个维度。
- Topic维度:假如一个Topic的消息量特别大,但集群水位压力还是很低,就可以扩大该Topic的队列数,Topic的队列数跟发送、消费速度成正比。
- Broker维度:如果集群水位很高了,需要扩容,直接加机器部署Broker就可以。Broker起来后向Namesrv注册,Producer、Consumer通过Namesrv 发现新Broker,立即跟该Broker直连,收发消息。
提高 Consumer 的消费能力
- 提高消费并行度:增加队列数和消费者数量,提高单个消费者的并行消费线程,参数 consumeThreadMax。
- 批处理消费,设置 consumeMessageBatchMaxSize 参数,这样一次能拿到多条消息,然后比如一个 update语句之前要执行十次,现在一次就执行完。
- 跳过非核心的消息,当负载很重的时候,为了保住那些核心的消息,设置那些非核心的消息,例如此时消息堆积 1W 条了之后,就直接返回消费成功,跳过非核心消息。
负载均衡
Consumer 会定期的获取 Topic 下的队列数,然后再去查找订阅了该 Topic 的同一消费组的所有消费者信息,默认的分配策略是类似分页排序分配。将队列排好序,然后消费者排好序,比如队列有 9 个,消费者有 3 个,那消费者-1 消费队列 0、1、2 的消息,消费者-2 消费队列 3、4、5,以此类推。所以如果负载太大,那么就加队列,加消费者,通过负载均衡机制就可以感知到重平衡,均匀负载。
消费重试
RocketMQ 会给每个消费组都设置一个重试队列,Topic 是 %RETRY%+consumerGroup,并且设定了很多重试级别来延迟重试的时间。为了利用 RocketMQ 的延时队列功能,重试的消息会先保存在 Topic 名称为“SCHEDULE_TOPIC_XXXX”的延迟队列,在消息的扩展字段里面会存储原来所属的 Topic 信息。delay 一段时间后再恢复到重试队列中,然后 Consumer 就会消费这个重试队列主题,得到之前的消息。如果超过一定的重试次数都消费失败,则会移入到死信队列,即 Topic %DLQ%" + ConsumerGroup 中,存储死信队列即认为消费成功,因为实在没辙了,暂时放过。然后我们可以通过人工来处理死信队列的这些消息。
事务性消息
它可以被认为是一个两阶段的提交消息实现,以确保分布式系统的最终一致性。事务性消息确保本地事务的执行和消息的发送可以原子地执行。使用限制:
- 没有调度和批处理支持。
- 为避免单条消息被检查次数过多,导致半队列消息堆积,我们默认将单条消息的检查次数限制为15次,但用户可以通过更改“transactionCheckMax”来更改此限制”参数在broker的配置中,如果一条消息的检查次数超过“transactionCheckMax”次,broker默认会丢弃这条消息,同时打印错误日志。用户可以通过重写“AbstractTransactionCheckListener”类来改变这种行为。
- 事务消息将在一定时间后检查,该时间由代理配置中的参数“transactionTimeout”确定。并且用户也可以在发送事务消息时通过设置用户属性“CHECK_IMMUNITY_TIME_IN_SECONDS”来改变这个限制,这个参数优先于“transactionMsgTimeout”参数。
- 一个事务性消息可能会被检查或消费不止一次。
- 提交给用户目标主题的消息reput可能会失败。目前,它取决于日志记录。高可用是由 RocketMQ 本身的高可用机制来保证的。如果要保证事务消息不丢失,保证事务完整性,推荐使用同步双写。机制。
- 事务性消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务性消息允许向后查询。MQ 服务器通过其生产者 ID 查询客户端。
事务性消息有三种状态:
- TransactionStatus.CommitTransaction:提交事务,表示允许消费者消费该消息。
- TransactionStatus.RollbackTransaction:回滚事务,表示该消息将被删除,不允许消费。
- TransactionStatus.Unknown:中间状态,表示需要MQ回查才能确定状态。