消息队列 RocketMQ 设计学习
消息队列 RocketMQ 设计学习
概述
一个消息队列的实现需要关注哪些问题点?需要实现哪些功能?
先说最通用的功能,任何系统都需要考虑的功能:
- 系统结构
- 高可用
- 高性能
再说,MQ 特有的功能:
- Message Order
- At Least Once
- 消息查询
- 消息堆积能力
- 事务
- 定时消息
- 回溯消费
详细设计
物理部署结构
如上图所示, RocketMQ的部署结构有以下特点:
- Name Server 是一个无状态节点,可集群部署,节点之间无任何信息同步。
- Broker 分 Master 和 Slave,需要手工通过配置来分配 Master 和 Slave 角色。每个 Broker 与 Name Server 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 Name Server。
- Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
- Consumer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。
跟 Kafka 不同的点在于:
- 这里的 Master/Slave 是人工指定的,而 Kafka 中是选举出来的。
- 因为没有了选举,所以 Name Server 之间也就不需要信息同步了,变成了简单的注册服务。
数据存储结构
如上图所示,RocketMQ采取了一种数据与索引分离的存储方法。
- CommitLog: 消息内容存储在 CommitLog 中,所有 topic 的消息都存在同一个文件中。
- Consume Queue: 每一个 topic 都有多个 consume queue,其中记录该 topic 的消息在 CommitLog 中的偏移量。consume queue 相当于 Kafka 的 partition,queue 的数目决定了 consumer 的并行度。
- 消息索引: 对字符串“Topic#消息键”建立的哈希索引,用于根据消息键查询消息在 CommitLog 中的 offset,进而可以查看消息内容。除了消息键,还可以设置特殊的 keys 用以查询。
高可用
RocketMQ做了以下的事情来保证系统的高可用:
- 多master部署,防止单点故障
- 消息冗余(主从结构),防止消息丢失
- 故障恢复
如果 Master 挂了怎么办?当 Master 挂了之后,对应 Slave 上的消息仍然可以消费,Producer 会将消息发往其他 Master。目前其缺点是 Slave 无法自动切换成 Master。
Consumer 消费数据时,应该请求 Master 还是 Slave 呢?
- 如果 Slave 可读,并且 Consumer 消费过慢时,推荐从 Slave 读。
- 如果 Slave 可读,消费也正常,返回当前 Broker。
- 如果 Slave 不可读,返回 Master。
如何判断是否消费过慢呢?RocketMQ是根据以下两个值进行判断的:
- a = 当前CommitLog的maxOffset - 需要消费消息的offset的结果
- b = RocketMQ可用内存的大小
如果a > b则判断为消息消费过慢,a > b 表示需要消费的消息一定不在内存中了,还需要读取文件,这样会给还需要写消息的 broker 带来一定的性能压力,所以这个时候 master 建议从 slave 读取消息。
高性能
RocketMQ 如何提升性能的呢?
- 充分利用Linux文件系统内存cache来提高性能。
- 零拷贝。
- 消费过慢的 Consumer 转移到 Slave 上去读,避免影响 Master 的性能。
- 异步、批量写可以提升性能,但相比同步可靠性要低一些。
Message Order
消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了3条消息,分别是订单创建,订单付款,订单完成。消费时,要按照这个顺序消费才能有意义。但是同时订单之间是可以并行消费的。
RocketMQ 可以严格的保证消息有序。
At Least Once
消息要保证可靠投递,即每个消息必须投递一次。
RocketMQ Consumer 先 pull 消息到本地,消费完成后,才向服务器返回 ack ,如果没有消费一定不会 ack 消息,所以 RocketMQ 可以很好的支持此特性。
消息查询
有时候,查一些线上问题时,需要根据消息 id 或一些关键字来查消息内容。RocketMQ 正好提供了索引机制,可以根据消息键或其他关键字来查询消息内容。
有时候查问题,还需要能够根据消息 id 找到该消息的传输轨迹,也就是被哪些 group 消费过,消费成功还是失败。
消息堆积能力
消息中间件的主要功能是异步解耦,还有个重要功能是挡住前端的数据洪峰,保证后端系统的稳定性,这就要求消息中间件具有一定的消息堆积能力。
评估消息堆积能力主要有以下四点:
- 消息能堆积多少条,多少字节?即消息的堆积容量。
- 消息堆积后,发消息的吞吐量大小,是否会受堆积影响?
- 消息堆积后,正常消费的Consumer是否会受影响?
- 消息堆积后,访问堆积在磁盘的消息时,吞吐量有多大?
事务
关于发送消息时的事物问题:分布式事务涉及到两阶段提交问题,在数据存储方面必然需要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 有哪些缺点呢?
- Master 宕机后,Slave 不能自动切换成 Master。
- 需要手工配置 Master 和 Slave,相比自动的选举,是一种退步。