起因

阿里巴巴团队使用 ActiveMQ 5.x处理消息,遇到瓶颈;而此时分布式流式处理引擎 Kafka 已经兴起,Kafka 存在高延迟、没有事务支持等功能就被放弃了,而阿里巴巴团队基于消息队列的基础模型开发了 RocketMQ,可以理解为 RocketMQ 为处理消息而生。

架构组件

rocketmq zookeeper集群部署 rocketmq 集群架构_长连接

NameServer

NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步,他们之间是独立的、并行的,各自保留了一份全部的 Broker、Topic 等集群信息。

  • 非常简单的 Topic 路由注册中心
  • 支持 Broker 的动态注册与发现
  • Broker 心跳检测
  • 为 Producer、Consumer 集群提供 Topic 路由功能
BrokerServer

rocketmq zookeeper集群部署 rocketmq 集群架构_偏移量_02

Broker主要负责消息的存储、投递和查询以及服务高可用保证,包含四个主要的模块。

  • Remoting Module:整个Broker的实体,负责处理来自clients端的请求
  • Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
  • Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能
  • HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能
  • Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询
Producer

消息发布的角色,支持分布式集群方式部署,无状态信息。

Consumer

消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制。

网络部署特点

rocketmq zookeeper集群部署 rocketmq 集群架构_java_03

  • NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
  • Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master。BrokerId为0表示Master,非0表示Slave。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。
  • Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic 服务的 Broker 节点中的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
  • Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Broker 节点中的Master、Slave建立长连接,且定时向Master、Slave发送心跳。

连接关系

rocketmq zookeeper集群部署 rocketmq 集群架构_偏移量_04

  • NameServer 是一个集群
  • Broker 是一个集群,分为主节点和从节点,主节点可以读写,从节点只能读取消息,并定时向所有的 NameServer 发送心跳信息,定时注册 Topic 信息到 NameServer 中
  • Producer 是一个集群
  • Consumer 是一个集群
  • Producer 随机建立一个长连接 NameServer,从中获取 Topic 信息,并与 Topic 所在 Master 建立长连接、发送消息、定时发送心跳信息。
  • Consumer 随机长连接一个 NameServer,从中获取 Topic 信息,并与 Topic 所在 Master 或 Slaver 建立长连接、发送消息、定时发送心跳信息。

工作流程

  • 启动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建立连接通道,开始消费消息。
消息存储整体架构

rocketmq zookeeper集群部署 rocketmq 集群架构_消息队列_05

rocketmq zookeeper集群部署 rocketmq 集群架构_消息队列_06

  • CommitLog:消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件;
  • ConsumeQueue:消息消费队列,引入的目的主要是提高消息消费的性能,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据ConsumeQueue来查找待消费的消息。其中,ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样consumequeue文件采取定长设计,每一个条目共20个字节,分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M;

rocketmq zookeeper集群部署 rocketmq 集群架构_长连接_07

  • IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是:$HOME/store/index/{fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现为hash索引。

在上面的RocketMQ的消息存储整体架构图中可以看出,RocketMQ采用的是混合型的存储结构,即为Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么Producer发送的消息就不会丢失。正因为如此,Consumer也就肯定有机会去消费这条消息。当无法拉取到消息后,可以等下一次消息拉取,同时服务端也支持长轮询模式,如果一个消息拉取请求未拉取到消息,Broker允许等待30s的时间,只要这段时间内有新消息到达,将直接返回给消费端。这里,RocketMQ的具体做法是,使用Broker端的后台服务线程—ReputMessageService不停地分发请求并异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)数据。

Consumer消费是已Topic 为粒度的,但是CommitLog是所有Topic消息的汇总存储,这时候需要一个已Topic为维度的commitLog文件offset的索引,便于消费这个Topic 下的数据,因此产生了 ConsumeQueue。相当于一个Topic下,有多个MessageQueue消息队列,然后将消息队列映射为ConsumeQueue消息消费队列,供Consumer快捷消费这个Topic下的数据。