rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq


文章目录

  • 1.主从复制
  • 2.刷盘及集群复制策略
  • 3.顺序写入
  • 4.零拷贝机制
  • 5.事务消息
  • 6.延迟消息
  • 7. 有序消息
  • 8.死信队列
  • 9.消息回溯


1.主从复制

RocketMQ和Kafka一样,在Producer和Consumer集群中都存在多个Master、Slave主从复制实例,其中:

  • Master节点对外提供读写能力
  • Slave节点对Master节点进行数据同步
  • 在Master节点宕机时进行选举,Slave升级为Master
    但是也有一些不同之处:RocketMQ中的NameServer是无状态的,每个NameServer节点都能对外提供注册路由服务,Broker元数据在启动时就会保存在NameServer的内存中;而Kafka基于Zookeeper来存储元数据,Zookeeper需要分为Master和Slave节点,由Master对外提供注册路由服务

2.刷盘及集群复制策略

RocketMQ支持同步写刷盘、异步写刷盘、同步主从复制、异步主从复制的复杂组合,可以根据服务所需要的写入性能以及可靠性来组合。

  • 异步刷盘方式:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。
  • 同步刷盘方式:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。

如果一个Broker组有Master和Slave,消息需要从Master复制到Slave上,有同步和异步两种复制方式。

  • 同步复制方式:等Master和Slave均写成功后才反馈给客户端写成功状态
  • 异步复制方式:只要Master写成功即可反馈给客户端写成功状态

这两种复制方式各有优劣,在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果Master出了故障,有些数据因为没有被写入Slave,有可能会丢失;在同步复制方式下,如果Master出故障,Slave上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统吞吐量。

同步复制和异步复制是通过Broker配置文件里的brokerRole参数进行设置的,这个参数可以被设置成ASYNC_MASTER、SYNC_MASTER、SLAVE三个值中的一个。

实际使用中为了兼顾写入性能和消息可靠性,可以把Master和Save配置成同步写入刷盘、主从之间配置成同步复制的方式。

Kafka自身不支持同步刷盘机制,默认就是异步、批量刷盘,但是有两种方式来减少写入消息丢失的可能性:

  • 手动调用Linux fsync函数,将PageCache中的数据强制刷盘到磁盘中
  • 通过配置将刷盘时间降低,提高刷盘频率
    Kafka也不支持同步主从复制,采用的是基于同步和异步之间的ISR机制来进行主从复制。简单地说,就是主从复制时不以全部Slave复制完成作为成功信号,而是对于复制列表中的Broker复制成功作为成功信号。

所以RocketMQ的数据丢失几率比Kafka会小很多,对于数据可靠性要求很高的场景要选用RocketMQ。

3.顺序写入

RocketMQ和Kafka写入数据都是顺序追加到磁盘中的,也就是所谓的顺序写入。

磁盘的随机写入性能是很差的,因为每写入一次都需要寻址,而顺序写入则是连续寻址,可以批量地找到写入位置,顺序写入磁盘的效率是随机写入的千倍。

RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成的,消息真正的物理存储文件是CommitLog, ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址,CommitLog以物理文件的方式存放,每台Broker上的CommitLog被本机器所有ConsumeQueue共享。

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq顺序消息_02

RocketMQ存储机制这样设计有以下几个好处:

  • 每台Broker只存在一个CommitLog顺序写,不会因为ConsumeQueue过多导致顺序写演化为随机写,可以大大提高写入效率
  • ConsumeQueue作为虚拟队列,对CommitLog读取是随机读取,利用操作系统的pagecache机制,可以批量地从磁盘读取,作为cache存到内存中,加速后续的读取速度,理论上来说能达到存内存读取的速度

Kafka的Partition为实际写入队列,也就是说当Partition数量过多时,一个Broker的写入会由顺序写演化为多个Partition的随机写,导致性能大幅下降。

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq事务消息_03

4.零拷贝机制

通常程序对数据的读写是非零拷贝的,这里的“拷贝”指的是在应用程序对硬盘中的数据或网卡的数据进行读取时,会有两次跨越用户态和内核态的拷贝过程:

  • 第一次拷贝,将数据从内核态缓冲区拷贝到用户态程序中
  • 第二次拷贝,将数据从用户态程序拷贝到内核态缓冲区中
    这样一次数据传输效率就会降低,零拷贝要解决的问题就是在数据传输过程中,不需要拷贝过程。

RocketMQ通过mmap机制来实现零拷贝,也就是将内存地址直接映射到磁盘地址,通过对内存的修改及flush操作直接读取和写入磁盘:

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq顺序消息_04

而Kafka则通过在内核态缓冲区中进行两次复制操作,来避免用户态-内核态的复制,来达到零拷贝:

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq介绍_05

5.事务消息

RocketMQ相较于Kafka提供了更高的消息可靠性,其中最突出的特性就是支持事务消息。
事务消息指的是生产者“处理本地事务”和“投递消息”这两个动作同时成功或失败。
假如有一个订单服务,在下单接口中有两个关键操作,一个是“生成订单”这个本地事务操作,一个是“投递订单消息”到Kafka中给其他消费者订阅,以执行下一步操作。一般的处理方式如下:

  1. 生成订单失败的情况:不进行订单消息投递,下单失败
  2. 生成订单成功且投递订单消息失败:尝试重复投递(比如3次),不成功则回滚“生产订单”操作,下单失败
  3. 生成订单成功且投递订单消息成功:下单成功

以上三种情况是最普通的情况,似乎并不需要事务消息的支持也能保证两个操作的原子性,但是如果考虑极端情况,比如机器宕机或断电,那么就会出现“生成订单”和“投递订单消息”无法同时成功或失败:

  1. 生成订单操作执行成功
  2. 此时机器宕机没有执行到“投递订单消息”这个步骤
  3. 此时就出现了“订单已经生成”但是“订单消息”没有被投递的情况

在RocketMQ中,提供了事务消息的解决方案来保证生产者的本地事务和投递消息这两个操作同时成功或失败,三个核心概念:

Half(Prepare) Message — 半消息(预处理消息)
半消息是一种特殊的消息类型,该状态的消息暂时不能被Consumer消费。当一条事务消息被成功投递到Broker上,但是Broker并没有接收到Producer发出的二次确认时,该事务消息就处于"暂时不可被消费"状态,该状态的事务消息被称为半消息。

消息状态

  1. COMMIT_MESSAGE:本地事务提交成功,消费方可以消费消息,不会执行消息状态回查
  2. ROLLBACK_MESSAGE:本地事务回滚,消费方不会消费这条消息,不会执行消息状态回查方法
  3. UNKNOW:该消息状态尚未确认,执行消息状态回查方法,确认该消息为COMMIT_MESSAGE或ROLLBACK_MESSAGE

消息状态回查
如果Broker检测到某条事务消息长时间处于半消息状态,则会主动向Producer端发起回查操作,查询该事务消息在Producer端的事务状态(Commit 或 Rollback)。

基于上述特性,使用RocketMQ可以很好地支持事务消息:

  • 首先投递一个订单半消息
  • 执行“生成订单”操作
  • 如果成功,则将半消息提交为COMMIT_MESSAGE状态,此时该消息可被消费
  • 如果失败,则将半消息提交为ROLLBACK_MESSAGE状态,此时该消息不可被消费

针对机器宕机的场景:

  • 首先投递一个订单半消息
  • 执行“生成订单”操作
  • 此时机器宕机,消息状态默认为UNKNOW
  • Broker查询到该消息状态为UNKNOW,进行消息状态回查确认
  • 如果回查发现“生成订单”操作失败,则将该消息设置为ROLLBACK_MESSAGE状态,反之设置为COMMIT_MESSAGE状态

6.延迟消息

生产者把消息发送到消息队列中以后,并不期望被立即消费,而是等待指定时间后才可以被消费者消费,这类消息通常被称为延迟消息。

在RocketMQ中,支持延迟消息,但是不支持任意时间精度的延迟消息,只支持特定级别的延迟消息,以等级排序分别为:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别,最高延迟时间为2小时。

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq介绍_06

延迟消息的原理是在投递消息时,会将其投递到SCHEDULE_XX这类Topic中,每个延迟等级会对应一个延迟队列。RocketMQ会启动一个后台定时任务,去判断该延迟队列中的消息是否已到执行时间,如果到了执行时间,那么就会将其复制一份,重新投递到原Topic,以达到延迟投递的目的。
所以如果消费者能力有限,并且延迟消息很多时,延迟可能会有较长时间的堆积,导致延迟执行不及时。

7. 有序消息

有序消息指的是消费者按照投递消息的顺序进行消费,这在需要严格有序的场景会使用到,比如订单场景:下单、付款、发货等消息的顺序不能够乱了。
RocketMQ和Kafka实现有序消息的原理一致,都是实现了分区级别的有序消息,即通过一定的策略,将相同订单下的消息发送到分区之中,来实现分区有序消费:

  • 生产者:按一定策略将同一集合消息发送到同一分区或队列下
  • 消费者:将并发度调整为1,避免对分区或队列的并发消费

8.死信队列

RocketMQ对于一定消费失败的消息会进行一定次数的重试,和Kafka不同的是,RocketMQ会额外提供一个重试队列和死信队列:

rocketmq 如何设置多个nameserver rocketmq多主原理_rocketmq核心原理_07

  • 重试队列:以%RETRY%开头的Topic,当消息消费失败时,会被移入该队列,重试消费仍然失败时,如果重试次数少于配置次数(默认为16次,指数级增加重试间隔)则会继续投递回该重试队列中,如果超出次数则会投递到死信队列。消费者消费Topic时,默认会消费%RETRY%Topic,无需配置。
  • 死信队列:当重试次数达到配置的阈值时,该消息会从重试队列中移除,并投递到死信队列中,死信队列默认以%DLQ%开头,需要用户自行配置对该Topic的消费,默认不进行处理。

9.消息回溯

RocketMQ还提供了消息回溯特性,在每条消息中都会设置全局唯一的MsgKey和MsgID,使用它们可以查询到该消息的消费情况。