RocketMQ 架构设计
消息队列实现了消息投放和消息消费间的解耦,实现了异步处理消息的功能。RocketMQ 作为消息中间件,在其存储消息的结构上实现了消息均衡投放、消息容灾、高可用(Dledger
主从切换)、自动故障转移特点。
先引入以下几个概念:
Broker
:实际存储消息的节点,接收来自生产者的消息,提供给消费者消费;一般以一个集群的形式提供服务,防止单点故障;Topic
:一类或一大类消息的集合,比如订单相关的消息包括(订单已下单、订单已支付等等....);MessageQueue
:消息队列对 Topic 进一步划分,将同一个 Topic 的消息均匀分布在多个消息队列中,其中每个消息队列存在于一个 Broker 上,这样保证了出现故障时不会丢失整个 Topic 消息,并且均衡分布防止了消息数据倾斜的问题。NameServer
:存储了整个系统的元数据,就想 Kafka 会采用Zookeeper
来存储管理集群元数据一样。NameServer 主要存储了 (1)集群中有哪些Topic;(2)这些Topic的 MessageQueue 存放在哪些 Broker上;(3)集群中有哪些活跃的 Broker;NameServer 为了应对单点故障,也是以集群的形式对外提供服务。
为了保证数据的安全完整性以及服务高可用,需要对数据做多备份,因此提供了 Master Broker
、Slave Broker
主从节点,主从之间会定期进行数据同步。但是如果 Master 出现宕机,如何重新选主成为一个问题,RocketMQ 4.5
之后采用 Dledger
实现高可用集群,类似于 Redis 集群的 Sentinel 能够进行自动的故障转移操作。Dledger
底层使用 Raft 协议算法重新选举新的 Master 对外提供服务。
RocketMQ 特点
1、顺序消费
1.1 分区有序
一个 Topic 对应多个 MessageQueue,那么在一个 MessageQueue 中的消息就是保持有序的,也叫做分区有序。
例如上图一个 Topic 包括了 3个 MessageQueue,一共四条消息投递到了不同的消息队列中,那么 MessageQueue-0 内部的 Message1、Message4 就可以认为是分区有序的。但是需要注意,消费者从消息队列中取数据消费是可以并行消费多个 MessageQueue 中消息的。例如这个例子,第一个消费到的消息不一定是 Message-1。
1.2 全局有序
那么如果现在有这样一组消息:创建订单、更新订单、完成订单。各个消息需要保证按照顺序消费,此时就需要全局有序的概念了。要做到全局有序,只能一个 Topic 分配一个 MessageQueue,这样就变成单分区下的有序消费,全局有序模式下不支持并行消费。
2、消息过滤
一个消息队列中存储了多个 Topic 相关的消息,现在有需求需要消费者只消费某一个 Topic 的消息,如何实现?
- 业务代码实现,消费者获取 MessageQueue 中所有消息,然后消费时判断 Topic;
- RocketMQ 给每个消息打上一个标签,消费者在消费时只需要订阅标签为特定 Topic 的消息,消费者就只会拉取到相关 Topic 的消息。
3、事务消息
这里的事务类似于我们熟知的 Mysql 事务,即一组操作要么全部成功,要么全部失败。
RocketMQ 所定义的事务是指把消息发送到那个服务的本地事务和将消息投递到 MQ 这个操作组成一个事务,它涉及到两个操作:(1)本地事务运行且成功;(2)消息投递到MQ成功。
例如上图我们将订单数据持久化到数据库 和 消息投递到MQ 两个操作作为一个事务。
4、延迟队列
RocketMQ 支持的延时队列只有 18个选项,分别是 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
。
实际上可以组合这些时间实现任意的等待时间,可以利用时间轮算法,参考 Dubbo 的时间轮算法实现。
5、死信队列
死信队列用于处理那些无法被正常消费的消息,这些消息超过了最大重试消费次数(16),会被存放到死信队列中,实际上也是存储在一个 Topic 中,所有死信队列都会带上 %DLQ%
的前缀,然后跟上消费者组的名称,并且这个 Topic 对消费者来说是不可见的。
死信队列中的消息最多只能存放 3 天,并且可以通过 Dashboard
实现对这些消息的重放。
RocketMQ——Message
Message 作为消息的载体,其主要包括以下属性:
Topic
:消息主题,消息类型;Flag
:Properties
:消息属性,内部包含消息的标签 Tag;body
:消息体内容;TransactionId
:事务ID;