消息队列 RocketMQ 设计学习

消息队列 RocketMQ 设计学习

概述

一个消息队列的实现需要关注哪些问题点?需要实现哪些功能?

先说最通用的功能,任何系统都需要考虑的功能:

  1. 系统结构
  2. 高可用
  3. 高性能

再说,MQ 特有的功能:

  1. Message Order
  2. At Least Once
  3. 消息查询
  4. 消息堆积能力
  5. 事务
  6. 定时消息
  7. 回溯消费

详细设计

物理部署结构

消息队列 RocketMQ 设计学习
如上图所示, RocketMQ的部署结构有以下特点:

  1. Name Server 是一个无状态节点,可集群部署,节点之间无任何信息同步。
  2. Broker 分 Master 和 Slave,需要手工通过配置来分配 Master 和 Slave 角色。每个 Broker 与 Name Server 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 Name Server。
  3. Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
  4. Consumer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

跟 Kafka 不同的点在于:

  1. 这里的 Master/Slave 是人工指定的,而 Kafka 中是选举出来的。
  2. 因为没有了选举,所以 Name Server 之间也就不需要信息同步了,变成了简单的注册服务。

数据存储结构

消息队列 RocketMQ 设计学习
如上图所示,RocketMQ采取了一种数据与索引分离的存储方法。

  1. CommitLog: 消息内容存储在 CommitLog 中,所有 topic 的消息都存在同一个文件中。
  2. Consume Queue: 每一个 topic 都有多个 consume queue,其中记录该 topic 的消息在 CommitLog 中的偏移量。consume queue 相当于 Kafka 的 partition,queue 的数目决定了 consumer 的并行度。
  3. 消息索引: 对字符串“Topic#消息键”建立的哈希索引,用于根据消息键查询消息在 CommitLog 中的 offset,进而可以查看消息内容。除了消息键,还可以设置特殊的 keys 用以查询。

高可用

RocketMQ做了以下的事情来保证系统的高可用:

  1. 多master部署,防止单点故障
  2. 消息冗余(主从结构),防止消息丢失
  3. 故障恢复

如果 Master 挂了怎么办?当 Master 挂了之后,对应 Slave 上的消息仍然可以消费,Producer 会将消息发往其他 Master。目前其缺点是 Slave 无法自动切换成 Master。

Consumer 消费数据时,应该请求 Master 还是 Slave 呢?

  1. 如果 Slave 可读,并且 Consumer 消费过慢时,推荐从 Slave 读。
  2. 如果 Slave 可读,消费也正常,返回当前 Broker。
  3. 如果 Slave 不可读,返回 Master。

如何判断是否消费过慢呢?RocketMQ是根据以下两个值进行判断的:

  1. a = 当前CommitLog的maxOffset - 需要消费消息的offset的结果
  2. b = RocketMQ可用内存的大小

如果a > b则判断为消息消费过慢,a > b 表示需要消费的消息一定不在内存中了,还需要读取文件,这样会给还需要写消息的 broker 带来一定的性能压力,所以这个时候 master 建议从 slave 读取消息。

高性能

RocketMQ 如何提升性能的呢?

  1. 充分利用Linux文件系统内存cache来提高性能。
  2. 零拷贝
  3. 消费过慢的 Consumer 转移到 Slave 上去读,避免影响 Master 的性能。
  4. 异步、批量写可以提升性能,但相比同步可靠性要低一些。

Message Order

消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了3条消息,分别是订单创建,订单付款,订单完成。消费时,要按照这个顺序消费才能有意义。但是同时订单之间是可以并行消费的。

RocketMQ 可以严格的保证消息有序。

At Least Once

消息要保证可靠投递,即每个消息必须投递一次。

RocketMQ Consumer 先 pull 消息到本地,消费完成后,才向服务器返回 ack ,如果没有消费一定不会 ack 消息,所以 RocketMQ 可以很好的支持此特性。

消息查询

有时候,查一些线上问题时,需要根据消息 id 或一些关键字来查消息内容。RocketMQ 正好提供了索引机制,可以根据消息键或其他关键字来查询消息内容。

有时候查问题,还需要能够根据消息 id 找到该消息的传输轨迹,也就是被哪些 group 消费过,消费成功还是失败。

消息堆积能力

消息中间件的主要功能是异步解耦,还有个重要功能是挡住前端的数据洪峰,保证后端系统的稳定性,这就要求消息中间件具有一定的消息堆积能力。

评估消息堆积能力主要有以下四点:

  1. 消息能堆积多少条,多少字节?即消息的堆积容量。
  2. 消息堆积后,发消息的吞吐量大小,是否会受堆积影响?
  3. 消息堆积后,正常消费的Consumer是否会受影响?
  4. 消息堆积后,访问堆积在磁盘的消息时,吞吐量有多大?

事务

关于发送消息时的事物问题:分布式事务涉及到两阶段提交问题,在数据存储方面必然需要KV存储的支持,因为第二阶段的提交回滚需要修改消息状态,一定涉及到根据Key去查找Message的动作。RocketMQ在第二阶段绕过了根据Key去查找Message的问题,采用第一阶段发送Prepared消息时,拿到了消息的Offset,第二阶段通过Offset去访问消息,并修改状态,Offset就是数据的地址。

定时消息

定时消息是指消息发到Broker后,不能立刻被Consumer消费,要到特定的时间点或者等待特定的时间后才能被消费。

如果要支持任意的时间精度,在Broker层面,必须要做消息排序,如果再涉及到持久化,那么消息排序要不可避免的产生巨大性能开销。因此,RocketMQ支持定时消息,但是不支持任意时间精度,支持特定的level,例如定时5s,10s,1m等。

回溯消费

回溯消费是指Consumer已经消费成功的消息,由于业务上需求需要重新消费,要支持此功能,Broker在向Consumer投递成功消息后,消息仍然需要保留。并且重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据,那么Broker要提供一种机制,可以按照时间维度来回退消费进度。

RocketMQ支持按照时间回溯消费,时间维度精确到毫秒,可以向前回溯,也可以向后回溯。

这功能一般只有在处理系统故障的遗留问题时才需要吧。

缺点

RocketMQ 有哪些缺点呢?

  1. Master 宕机后,Slave 不能自动切换成 Master。
  2. 需要手工配置 Master 和 Slave,相比自动的选举,是一种退步。

参考资料

  1. RocketMQ 消息索引流程
  2. 十分钟入门 RocketMQ
  3. RocketMQ 高可用
  4. RocketMQ 的零拷贝技术