RocketMQ原理之高可用

RocketMQ 中的高可用机制

RocketMQ原理之高可用_第1张图片
RocketMQ 分布式集群是通过 Master 和 Slave 的配合达到高可用性的。

Master 和 Slave 的区别:在 Broker 的配置文件中,参数 brokerId 的值为 0 表明这个 Broker 是 Master,大于 0 表明这个 Broker 是 Slave,同时 brokerRole 参数也会说明这个 Broker 是 Master 还是 Slave。

Master 角色的 Broker 支持读和写,Slave 角色的 Broker 仅支持读,也就是 Producer 只能和 Master 角色的 Broker 连接写入消息;Consumer 可以连接 Master 角色的 Broker,也可以连接 Slave 角色的 Broker 来读取消息。

集群部署模式

单 master 模式

也就是只有一个 master 节点,称不上是集群,一旦这个 master 节点宕机,那么整个服务就不可用。

多 master 模式

多个 master 节点组成集群,单个 master 节点宕机或者重启对应用没有影响。

优点:所有模式中性能最高。在多主的架构体系下,无论使用客户端还是管理界面创建Topic,一个Topic都会创建个份队列在多主中(默认是 4 个的话,双主就会有 8 个队列), 每台主 4 个队列,所以双主可以提高性能,一个 Topic 的分布在不同的 master,方便进行横向拓展。

缺点:单个 master 节点宕机期间,未被消费的消息在节点恢复之前不可用,消息的实时性就受到影响。

多 master 多 slave 异步复制模式

在多 master 模式的基础上,每个 master 节点都有至少一个对应的 slave。master 节点可读可写,但是 slave 只能读不能写,类似于 mysql 的主备模式。

优点:一般情况下都是master消费,在 master 宕机或超过负载时,消费者可以从 slave 读取消息,消息的实时性不会受影响,性能几乎和多 master 一样。

缺点:使用异步复制的同步方式有可能会有消息丢失的问题。(Master 宕机后,生产者发送的消息没有消费完,同时到 Slave 节点的数据也没有同步完)

多 master 多 slave 主从同步复制+异步刷盘(最优推荐)

对数据要求较高的场景,主从同步复制方式,保存数据热备份,通过异步刷盘方式,保证 rocketMQ 高吞吐量。

优点:主从同步复制模式能保证数据不丢失。

缺点:发送单个消息响应时间会略长,性能相比异步复制低 10%左右。

Dlegder

类似于 Zookeeper 的集群选举模式

刷盘与主从同步

生产时首先将消息写入到 MappedFile,内存映射文件,然后根据刷盘策略刷写到磁盘。大致的步骤可以理解成使用 MMAP 中的 MappedByteBuffer 中实际用 flip().
RocketMQ原理之高可用_第2张图片
RocketMQ 的刷盘是把消息存储到磁盘上的,这样既能保证断电后恢复, 又可以让存储的消息量超出内存的限制。RocketMQ 为了提高性能,会尽可 能地保证磁盘的顺序写。消息在通过 Producer 写入 RocketMQ 的时候,有两种写磁盘方式,同步刷盘和异步刷盘。

同步刷盘

SYNC_FLUSH(同步刷盘):生产者发送的每一条消息都在保存到磁盘成功后才返回告诉生产者成功。这种方式不会存在消息丢失的问题,但是有很大的磁盘 IO 开销,性能有一定影响。

异步刷盘

ASYNC_FLUSH(异步刷盘):生产者发送的每一条消息并不是立即保存到磁盘,而是暂时缓存起来,然后就返回生产者成功。随后再异步的将缓存数据保存到磁盘,有两种情况:

  1. 是定期将缓存中更新的数据进行刷盘,
  2. 是当缓存中更新的数据条数达到某一设定值后进行刷盘。这种异步的方式会存在 消息丢失(在还未来得及同步到磁盘的时候宕机),但是性能很好。默认是这种模式。
    RocketMQ原理之高可用_第3张图片
    4.9.3 版本中默认值下是异步刷盘,如下图:
    RocketMQ原理之高可用_第4张图片

主从同步复制

集群环境下需要部署多个 Broker,Broker 分为两种角色:一种是 master,即可以写也可以读,其 brokerId=0,只能有一个;另外一种是 slave,只允许读,其 brokerId 为非 0。一个 master 与多个 slave 通过指定相同的 brokerClusterName 被归为一个 broker set(broker 集)。通常生产环境中,我们至少 需要 2 个 broker set。Slave 是复制 master 的数据。一个 Broker 组有 Master 和 Slave,消息需要从 Master 复制到 Slave 上,有同步和异步两种复制方式。

主从同步复制方式(Sync Broker):生产者发送的每一条消息都至少同步复制到一个 slave 后才返回告诉生产者成功,即“同步双写”

在同步复制方式下,如果 Master 出故障, Slave 上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统吞吐量。

主从异步复制

主从异步复制方式(Async Broker):生产者发送的每一条消息只要写入 master 就返回告诉生产者成功。然后再“异步复制”到 slave。

在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果 Master 出了故障,有些数据因为没有被写 入 Slave,有可能会丢失;
同步复制和异步复制是通过 Broker 配置文件里的 brokerRole 参数进行设置的,这个参数可以被设置成 ASYNC_MASTER、 SYNC_MASTER、SLAVE 三个值中的一个。

配置参数及意义

brokerId=0 代表主
brokerId=1 代表从(大于 0 都代表从) 
brokerRole=SYNC_MASTER 同步复制(主从) 
brokerRole=ASYNC_MASTER 异步复制(主从) 
flushDiskType=SYNC_FLUSH 同步刷盘 
flushDiskType=ASYNC_FLUSH 异步刷盘

搭建双主双从同步复制+异步刷盘

参考官方文档:https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md

消息生产的高可用机制

RocketMQ原理之高可用_第5张图片

在创建 Topic 的时候,把 Topic 的多个 Message Queue 创建在多个 Broker 组上(相同 Broker 名称,不同 brokerId 的机器组成一个 Broker 组),这样当一个 Broker 组的 Master 不可用后,其他组的 Master 仍然可用,Producer 仍然可以发送消息。

RocketMQ 目前不支持把 Slave 自动转成 Master,如果机器资源不足, 需要把 Slave 转成 Master,则要手动停止 Slave 角色的 Broker,更改配置文件, 用新的配置文件启动 Broker。

高可用消息生产流程

RocketMQ原理之高可用_第6张图片

  1. TopicA 创建在双主中,BrokerA 和 BrokerB 中,每一个 Broker 中有 4 个队列
  2. 选择队列是,默认是使用轮训的方式,比如发送一条消息 A 时,选择 BrokerA 中的 Q4
  3. 如果发送成功,消息 A 发结束。
  4. 如果消息发送失败,默认会采用重试机制,默认重试2次。
    RocketMQ原理之高可用_第7张图片
  5. 如果发生了消息发送失败,会有故障规避机制,默认生效。

如果是 BrokerA 宕机,上一次路由选择的是 BrokerA 中的 Q4,那么再次重发的队列选择是 BrokerA 中的 Q1,这里的问题就是消息发送很大可能再次失败。即尽管会重试 2 次,但都是发送给同一个 Broker 处理,此过程会显得不那么靠谱,即大概率还是会失败,那这样重试的意义将大打折扣。

为了解决该问题,引入了故障规避机制,在消息重试的时候,会尽量规避上一次发送的 Broker。回到上述示例,当消息发往 BrokerA Q4 队列时返回发送失败,那重试的时候,会先排除 BrokerA 中所有队列,选择 BrokerB Q1 队列,以增大消息发送的成功率。

RocketMQ 提供了两种规避策略,该参数由 sendLatencyFaultEnable 控制,默认为不开启。
RocketMQ原理之高可用_第8张图片
sendLatencyFaultEnable 设置为 false:默认值,不开启,**延迟规避策略只在重试时生效。**例如在一次消息发送过程中如果遇到消息发送失败,重试时规避 BrokerA,但是在下一次消息发送时,还是会选择 BrokerA 的队列进行发送,只有继续发送失败后,重试时才规避 BrokerA。

sendLatencyFaultEnable 设置为 true:开启延迟规避机制。一旦消息发送失败会将 BrokerA “悲观”地认为在接下来的一段时间内都不可用,在未来某一段时间内所有的客户端不会向该 Broker 发送消息。这个延迟时间就是通过 notAvailableDuration、latencyMax 共同计算的,就首先计算本次消息发送失败所耗的时延,然后对应 latencyMax 中哪个区间,即计算在 latencyMax 的下标,然后返回 notAvailableDuration 同一个下标对应的延迟值。

如果所有的 Broker 都触发了故障规避,并且 Broker 只是那一瞬间压力大,那岂不是明明存在可用的 Broker,但经过你这样规避,反倒是没有 Broker 可用来,那岂不是更糟糕了。所以 RocketMQ 默认不启用 Broker 故障延迟机制。

消息消费的高可用机制

主从的高可用原理

在 Consumer 的配置文件中,并不需要设置是从 Master 读还是从 Slave 读,当 Master 不可用或者繁忙的时候,Consumer 会被自动切换到从 Slave 读。 有了自动切换 Consumer 这种机制,当一个 Master 角色的机器出现故障后,Consumer 仍然可以从 Slave 读取消息,不影响 Consumer 程序。这就达到 了消费端的高可用性。

Master 不可用这个很容易理解,那什么是 Master 繁忙呢?这个繁忙其实是 RocketMQ 服务器的内存不够导致的。

源码分析:org.apache.rocketmq.store. DefaultMessageStore#getMessage 方法:
RocketMQ原理之高可用_第9张图片
当前需要拉取的消息已经超过常驻内存的大小,表示主服务器繁忙,此时才建议从从服务器拉取。

消息消费的重试

消费端如果发生消息失败,没有提交成功,消息默认情况下会进入重试队列中。
在这里插入图片描述
注意重试队列的名字其实是跟消费群组有关,不是主题,因为一个主题可以有多个群组消费,所以要注意
RocketMQ原理之高可用_第10张图片

顺序消息的重试

对于顺序消息,当消费者消费消息失败后,消息队列 RocketMQ 会自动不断进行消息重试(每次间隔时间为 1 秒),这时,应用会出现消息消费被阻塞的情况。因此,在使用顺序消息时,务必保证应用能够及时监控并处理消费失败的情况,避免阻塞现象的发生。

所以玩顺序消息时。consume 消费消息失败时,不能返回 reconsume_later,这样会导致乱序,应该返回 suspend_current_queue_a_moment,意思是先等一会,一会儿再处理这批消息,而不是放到重试队列里。

无序消息的重试

无序消息(普通、定时、延时、事务消息),当消费者消费消息失败时,可以通过设置返回状态达到消息重试的结果。无序消息的重试只针对集群消费方式生效;广播方式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。

重试次数

如果消息重试 16 次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的 4 小 时 46 分钟之内进行 16 次重试,超过这个时间范围消息将不再重试投递。

注意: 一条消息无论重试多少次,这些重试消息的 Message ID 不会改变。

重试配置

集群消费方式下,消息消费失败后期望消息重试,需要在消息监听器接口的实现中明确进行配置(三种方式任选一种):

  • 返回 RECONSUME_LATER (推荐)
  • 返回 Null
  • 抛出异常

集群消费方式下,消息失败后期望消息不重试,需要捕获消费逻辑中可能抛出的异常,最终返回 CONSUME_SUCCESS,此后这条消息将不会再重试。

自定义消息最大重试次数

消息队列 RocketMQ 允许 Consumer 启动的时候设置最大重试次数,重试时间间隔将按照如下策略:

  • 最大重试次数小于等于 16 次,则重试时间间隔同上表描述。
  • 最大重试次数大于 16 次,超过 16 次的重试时间间隔均为每次 2 小时。
    在这里插入图片描述
    消息最大重试次数的设置对相同 Group ID 下的所有 Consumer 实例有效。如果只对相同 Group ID 下两个 Consumer 实例中的其中一个设置了 MaxReconsumeTimes,那么该配置对两个 Consumer 实例均生效。 配置采用覆盖的方式生效,即最后启动的 Consumer 实例会覆盖之前的启动实例的配置。

死信队列

当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正 确地消费该消息,此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。

在消息队列 RocketMQ 中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列 (Dead-Letter Queue)。

死信特性

死信消息具有以下特性:

  • 不会再被消费者正常消费。
  • 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。

死信队列具有以下特性:

  • 不会再被消费者正常消费。
  • 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
  • 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列。
  • 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。

查看死信消息

在控制台查询出现死信队列的主题信息
RocketMQ原理之高可用_第11张图片
在消息界面根据主题查询死信消息,选择重新发送消息。

一条消息进入死信队列,意味着某些因素导致消费者无法正常消费该消息,因此,通常需要对其进行特殊处理。排查可疑因素并解决问题后,可以在消息队列 RocketMQ 控制台重新发送该消息,让消费者重新消费一次。

负载均衡

Producer 负载均衡

Producer 端,每个实例在发消息的时候,默认会轮询所有的 message queue 发送,以达到让消息平均落在不同的 queue 上。而由于 queue 可以散落在不同 的 broker,所以消息就发送到不同的 broker 下。

Consumer 负载均衡

集群模式

在集群消费模式下,每条消息只需要投递到订阅这个 topic 的 Consumer Group 下的一个实例即可。RocketMQ 采用主动拉取的方式拉取并消费消息,在拉 取的时候需要明确指定拉取哪一条 message queue。

每当实例的数量有变更,都会触发一次所有实例的负载均衡,这时候会按照 queue 的数量和实例的数量平均分配 queue 给每个实例。 默认的分配算法是 AllocateMessageQueueAveragely。

还有另外一种平均的算法是 AllocateMessageQueueAveragelyByCircle,也是平均分摊每一条 queue,只是以环状轮流分 queue 的形式

如下图:
RocketMQ原理之高可用_第12张图片

需要注意的是,集群模式下,queue 都是只允许分配只一个实例,这是由于如果多个实例同时消费一个 queue 的消息,由于拉取哪些消息是 consumer 主 动控制的,那样会导致同一个消息在不同的实例下被消费多次,所以算法上都是一个 queue 只分给一个 consumer 实例,一个 consumer 实例可以允许同 时分到不同的 queue。

通过增加 consumer 实例去分摊 queue 的消费,可以起到水平扩展的消费能力的作用。而有实例下线的时候,会重新触发负载均衡,这时候原来分配到的 queue 将分配到其他实例上继续消费。

但是如果 consumer 实例的数量比 message queue 的总数量还多的话,多出来的 consumer 实例将无法分到 queue,也就无法消费到消息,也就无法起到分 摊负载的作用了。所以需要控制让 queue 的总数量大于等于 consumer 的数量。

广播模式

由于广播模式下要求一条消息需要投递到一个消费组下面所有的消费者实例,所以也就没有消息被分摊消费的说法。 在实现上,其中一个不同就是在 consumer 分配 queue 的时候,所有 consumer 都分到所有的 queue。

你可能感兴趣的:(RocketMQ,java,中间件)