详细介绍了RocketMQ的技术架构以及集群启动工作流程。
文章目录
- 1 RocketMQ的技术架构
- 2 NameServer
- 3 ZooKeeper和NameServer
- 4 Broker
- 5 部署架构
1 RocketMQ的技术架构
RocketMQ的技术架构图如下:
RocketMQ架构上主要分为四部分,如上图所示:
- Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。支持多种多种负载均衡投递模式。
- Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。
- NameServer:名称服务,充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,无状态,没有信息交换。
- BrokerServer:Broker主要负责消息的存储、投递和查询以及服务高可用保证。Broker也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
2 NameServer
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo或者kafka中的zookeeper,Spring Cloud 中的 Eureka,NameServer支持Broker的动态注册与发现。主要包括两个功能:
- Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活。
- 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。Producer在投递消息之前和Conumser在拉取消息之前,都会通过NameServer获取整个Broker集群的路由信息,从而进行消息的投递和消费。
NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。
NameServer是无状态的。状态的有无实际上就是数据是否会做存储,有状态的话数据会被持久化,无状态的服务可以理解就是一个内存服务,NameServer本身也是一个内存服务,所有数据都存储在内存中,重启之后都会丢失。
3 ZooKeeper和NameServer
ZooKeeper 作为支持顺序一致性的中间件,在zookeeper进行leader选举的时候,它为了满足一致性,选举时集群不允许客户端读写,这将会丢失一定时间内的可用性,即CP原则。通常,再向zookeeper写数据时,如果同步的数据没有超过半数节点,那么同样不允许客户端读取。
而RocketMQ 需要一个注册中心实现服务的发现和注册,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,但必须保证高可用,就算是返回了包含不实的信息的结果也比什么都不返回要好,如果采用zookeeper来提供服务注册发现,如果某次选举时间过长(30 ~ 120s),那么将导致长时间获取不到任务服务的信息,造成严重的后果。因此,RocketMQ自制的NameServer实现的是AP。
另外,如果RocketMQ采用zookeeper作为注册中心,另一个非常麻烦的事儿就是这增加了系统和运维人员工作量复杂度,因为zookeeper和RocketMQ是两个不同的系统,如果要使用RocketMQ,就必须先安装Zookeeper服务,比较麻烦,并且Zookeeper集群的治理也不简单。
RocketMQ的架构设计决定了只需要一个轻量级的元数据服务器就足够了,只需要保持最终一致,因此,RocketMQ决定使用自制的NameServer来实现简单的路由管理服务,将元数据存储在RocketMQ内部,设计很简单,非常的轻量级。NameServer 集群间互不通信,因此它们之间的注册信息可能会不一致,这是一种去中心化的架构,所有注册信息保存在内存中(无状态),一台NameServer挂了也没关系,从另一台NameServer上拉取数据即可。
另外,关于Topic的路由信息都是Producer和Consumer定时(30s)主动去NameServer拉取的,因此当有新的服务器把信息注册到NameServer时,Producer和Consumer可能并不会马上知道。
此前的Kafka就严重依赖于ZooKeeper集群,而Kafka作为一个顶级的消息队列,竟然要依赖一个重量级的协调系统ZooKeeper,不得不说是一个笑话。实际上Kafka已经在尝试消弱Zookeeper的影响,最新的Kafka 2.8.0版本中,已经移除了对Zookeeper的依赖,通过内嵌的kraft进行自己的集群元数据管理,这不得不说是一种进步。
4 Broker
Broker为了实现消息的存储、投递和查询以及服务高可用保证,包含了以下几个重要子模块:
- Remoting Module:整个Broker的实体,负责处理来自clients端的请求。
- Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
- Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
- HA Service:High Availability高可用服务,数据分片存储到不同的服务器实例上,并提供Master Broker 和 Slave Broker之间的数据同步功能。
- Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。
5 部署架构
图中的四个角色都支持分布式集群方式部署,实现了高性能,天然支持高可用(HA),它们有如下特点:
- NameServer是一个几乎无状态节点,为保证HA可集群部署,节点之间无任何信息同步,这是一种去中心化的部署。
- Broker部署相对复杂,做了集群并且还进行了主从部署,进一步提升了可用性。Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。
- 每个Broker与NameServer集群中的所有节点建立长连接,每隔30s向所有 Nameserver 发送心跳(附带最新Topic信息),底层的通信和连接都是基于Netty实现的。如果Topic过多,那么一次心跳传输的数据就会很多,可能导致网络传输失败,导致NameServer误认为Broker心跳失败。
- NameServer每隔10s,扫描所有还存活的Broker连接,若某个连接2分钟内没有发送心跳数据,则判定断开连接。
- 当NameServer根据心跳超时主动关闭与某个Broker的连接之后,会更新Topic与Broker的对应关系,但不会通知生产者和消费者。
- Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,如果该NameServer挂掉,生产者会自动连接下一个NameServer,直到有可用连接为止,并能自动重连。Producer完全无状态,可集群部署。
- 每隔30s从NameServer获取最新Topic路由信息,并向提供Topic 服务的Master建立长连接,且定时向Master发送心跳。如果某个Broker宕机,生产者最多要30秒才能感知,在此期间,发往该broker的消息均发送失败。
- Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,如果该NameServer挂掉,消费者会自动连接下一个NameServer,直到有可用连接为止,并能自动重连。
- 每隔30s从NameServer获取最新Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。如果某个Broker宕机,消费者最多要30秒才能感知。
- Consumer既可以从Master订阅消息,也可以从Slave订阅消息,消费者在向Master拉取消息时,Master服务器会根据拉取偏移量与最大偏移量的距离(判断是否读老消息,产生读I/O),以及Master是否可用,Slave是否可读等因素建议下一次是从Master还是Slave拉取。
结合部署架构图,描述集群工作流程:
- 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
- Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
- 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
- Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
- Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。