本章,我们先来看下RocketMQ的基本架构,涉及哪些核心组件,以及各个组件之间的关系又是怎样的。
一、基本架构
在RockectMQ中,一共有四个核心组件:NameServer、Broker、Producer、Customer,它们之间的基本关系可以用RocketMQ官方的一张图表示:
上图中,Broker Cluster就是各个RocketMQ进程,Producer Cluster和Consumer Cluster分别是生产者和消费者,NameServer Cluster是路由中心。
看不懂?没关系,我们下面将一一分析上述的各个组件。
1.1 Broker
我们在每台机器上部署的RocketMQ进程,就称为Broker。Broker主要负责消息的存储,一般来说,在一台配置好一点的机器上部署单个Broker实例后,可以抗大约10万QPS的请求。
我们可以看上图,Broker本身可以构建成一个集群,我们的所有消息数据是以数据分片的形式分布在各个Broker节点上,也就是每个Broker节点保存总数据的一部分。此外,为了保证集群的可用性,每个Broker节点都有自己的副本(Slave),它们之间会进行数据同步。
Broker集群的整个架构就是我们在分布式篇中讲过的数据分散集群架构和Master/Slave架构的结合。
1.2 NameServer
NameServer是RocketMQ的路由中心,每一个NameServer节点都保存着全量的路由信息。因为Broker是集群部署,所以当生产者发送消息时,需要知道将消息发送到哪个Broker,当消息者获取消息时,也需要知道从哪个Broker获取消息。
每一个Broker节点(包括Slave)都会通过心跳机制(TCP长连接),将自己的基本信息注册到每一个NameServer中,这样Producer和Consumer就可以从NameServer拉取到路由消息。
默认情况下,每个Broker会每隔30s给所有的NameServer发送心跳,告诉NameServer自己还活着;与此同时,每个NameServer每隔10s检查一下各个Broker的最近一次心跳时间,如果发现某个Broker超过120s都没发送心跳,就认为这个Broker已经挂掉了,会将其从路由信息里移除。
所谓的路由信息,可以理解为Broker集群里的各个Broker的自身信息。
1.3 Producer
生产者,用于生产消息,会定时从NameServer拉取路由信息,然后根据路由信息与指定的Broker建立TCP长连接,从而将消息发送到Broker中。
1.4 Consumer
消费者,用于消费消息,会定时从NameServer拉取路由信息,然后根据路由信息与指定的Broker建立TCP长连接,从而从Broker拉取消息。
二、高可用
了解完RocketMQ的基本架构后,我们先来看看RocketMQ是如何实现高可用的。由于Producer和Consumer是直接与我们的客户端程序相关的,可用性由我们自己来保证,所以重点看下NameServer和Broker。
2.1 NameServer的可用性
NameServer管理着Broker的基本信息,如果NameServer挂掉了,那么生产者和消费者就找不到Broker了,所以NameServer需要以集群方式部署来实现高可用。在RocketMQ中,每个NameServer都保存着Broker集群的所有Broker信息,所以就算一台NameServer服务器宕机了,还有其它NameServer可用。
2.2 Broker的可用性
每个Broker节点都是主从架构,所以就算主节点宕掉了,从节点依然可以提供服务。但这里就要思考两个问题:
主从节点之间如何进行数据同步?
RokectMQ是否具有故障自动转移机制(即主节点挂掉后,从节点自动成为主节点,不需要人工介入)?
对于第一点,每一个Slave-Broker节点都会去自己的Master节点那里拉取数据,以进行同步;
对于第二点,在RocketMQ4.5版本以前,如果Master节点挂掉了,需要手动选出一个Slave节点重新作为Master节点,效率很低。所以4.5版本后,RocketMQ引入了Dleger机制,采用Raft协议进行主从节点的选举,实现故障自动转移。
关于Raft协议,读者可以参考我的《分布式系统从理论到实战系列》,我也会在进阶篇中对Dleger机制的原理做详细讲解。
三、可扩展
RocketMQ之所以具有可扩展性,是因为每个Broker节点只保存整体数据的一部分,这样当数据量越来越大时,可以进行水平切分。如果读者对RabbitMQ有所了解就知道,RabbitMQ中的每个节点保存着全量数据,那么当数据量越来越大时,是没法水平扩展的,而RocketMQ通过数据分散集群的模式实现了水平扩展。
3.1 Topic和Tag
在RocketMQ中,每一个消息都有其所属的Topic,所谓Topic,就是数据集合的意思,是一个逻辑概念。
举个例子,假设我们的订单系统需要往MQ里发送订单消息,那此时就应该建立一个Topic,它的名字可以叫做:topic_orderInfo,也就是一个包含了所有订单消息的数据集合。然后生产者发送消息时,就必须指定好消息所属的Topic,消费者消费消息时,也需要指定从哪个Topic里获取消息。
Broker在存储消息时,每一个Topic中的所有消息数据可能会分散在不同的Broker节点上,我们可以在创建Topic时进行指定。比如,假设我们的topic_orderInfo包含900万条消息,我们指定其分散在3个Broker节点上,那么每个节点就包含300万条消息数据:
除了Topic外,还有一个Tag分类,区分在于 Topic 是一级分类,而 Tag 可以理解为是二级分类。
那到底什么时候该用 Topic,什么时候该用 Tag?建议如下:
消息类型是否一致:如普通消息、事务消息、定时(延时)消息、顺序消息,不同的消息类型使用不同的 Topic,无法通过 Tag 进行区分;
业务是否相关联:没有直接关联的消息,如淘宝交易消息,京东物流消息使用不同的 Topic 进行区分;而同样是天猫交易消息,电器类订单、女装类订单、化妆品类订单的消息可以用 Tag 进行区分。
消息优先级是否一致:如同样是物流消息,盒马必须小时内送达,天猫超市 24 小时内送达,淘宝物流则相对会慢一些,不同优先级的消息用不同的 Topic 进行区分。
消息量级是否相当:有些业务消息虽然量小但是实时性要求高,如果跟某些万亿量级的消息使用同一个 Topic,则有可能会因为过长的等待时间而“饿死”,此时需要将不同量级的消息进行拆分,使用不同的 Topic。
每个Broker都通过心跳机制告诉NameServer:我这里有哪些类型的Topic,每类Topic的哪些数据保存在我这。所以生产者才会知道向哪个Broker发送消息,消费者同理。
另外要注意:生产者只能往Master-Broker节点发送消息,消费既可以从Master-Broker节点消费消息,也可以从Slave-Broker节点消费消息,这个我们后面讲解Broker持久化原理时会详细介绍。
四、总结
本章,我们介绍了RocketMQ的基本架构,并对其中的NameServer、Broker、Producer、Customer这四个核心组件进行了简要讲解。RocketMQ实现高可用和可扩展的思路其实没什么新意,就是基于Raft协议的主从架构,以及数据分散集群模式。
如果读者对Spring Cloud有所了解,就会发现,RocketMQ的基本架构和Spring Cloud中的很多组件非常相似,比如NameServer,其实就是类似于Spring Cloud中的Eureka服务注册中心,读者也可以结合我的《分布式系统从理论到实战系列》进行比较。