RocketMQ核⼼概念

112 阅读9分钟

1.消息模型(Message Model)

RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责⽣ 产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程 中对应⼀台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可 以分⽚存储于不同的 Broker。Message Queue ⽤于存储消息的物理地址,每个Topic 中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实 例构成。

2.消息⽣产者(Producer)

负责⽣产消息,⼀般由业务系统负责⽣产消息。⼀个消息⽣产者会把业务应⽤系统 ⾥产⽣的消息发送到broker服务器。RocketMQ提供多种发送⽅式,同步发送、异步 发送、顺序发送、单向发送。同步和异步⽅式均需要Broker返回确认信息,单向发 送不需要。⽣产者组将多个⽣产者归为⼀组。⽤于保证⽣产者的⾼可⽤,⽐如在事务消息中回 查本地事务状态,需要⽣产者具备⾼可⽤的特性,才能完成整个任务。

3.消息消费者(Consumer)

负责消费消息,⼀般是后台系统负责异步消费。⼀个消息消费者会从Broker服务器 拉取消息、并将其提供给应⽤程序。从⽤户应⽤的⻆度⽽⾔提供了两种消费形式: 拉取式消费、推动式消费。 消费者组将多个消息消费者归为⼀组,⽤于保证消费者的⾼可⽤和⾼性能。

4.主题(Topic)

表示⼀类消息的集合,每个主题包含若⼲条消息,每条消息只能属于⼀个主题, 是RocketMQ进⾏消息订阅的基本单位。

5.代理服务器(Broker Server)

消息中转⻆⾊,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收 从⽣产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存 储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。

6.名字服务(Name Server)

名称服务充当路由消息的提供者。⽣产者或消费者能够通过名字服务查找各主题相 应的Broker IP列表。多个Namesrv实例组成集群,但相互独⽴,没有信息交换。

7.拉取式消费(Pull Consumer)

Consumer消费的⼀种类型,应⽤通常主动调⽤Consumer的拉消息⽅法从Broker服 务器拉消息、主动权由应⽤控制。⼀旦获取了批量消息,应⽤就会启动消费过程。

8.推动式消费(Push Consumer)

Consumer消费的⼀种类型,该模式下Broker收到数据后会主动推送给消费端,该 消费模式⼀般实时性较⾼。

9.⽣产者组(Producer Group)

同⼀类Producer的集合,这类Producer发送同⼀类消息且发送逻辑⼀致。如果发送 的是事务消息且原始⽣产者在发送之后崩溃,则Broker服务器会联系同⼀⽣产者组 的其他⽣产者实例以提交或回溯消费。

10.消费者组(Consumer Group)

同⼀类Consumer的集合,这类Consumer通常消费同⼀类消息且消费逻辑⼀致。消 费者组使得在消息消费⽅⾯,实现负载均衡和容错的⽬标变得⾮常容易。要注意的 是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ ⽀持两种消息模 式:集群消费(Clustering)和⼴播消费(Broadcasting)。

11.集群消费(Clustering)

集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。

12.⼴播消费(Broadcasting)

⼴播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。

13.普通顺序消息(Normal Ordered Message)

普通顺序消费模式下,消费者通过同⼀个消费队列收到的消息是有顺序的,不同消 息队列收到的消息则可能是⽆顺序的。

14.严格顺序消息(Strictly Ordered Message)

严格顺序消息模式下,消费者收到的所有消息均是有顺序的。

15.消息(Message)

消息系统所传输信息的物理载体,⽣产和消费数据的最⼩单位,每条消息必须属于 ⼀个主题。RocketMQ中每个消息拥有唯⼀的Message ID,且可以携带具有业务标识 的Key。系统提供了通过Message ID和Key查询消息的功能。

16.标签(Tag)

为消息设置的标志,⽤于同⼀主题下区分不同类型的消息。来⾃同⼀业务单元的消 息,可以根据不同业务⽬的在同⼀主题下设置不同标签。标签能够有效地保持代码 的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对 不同⼦主题的不同消费逻辑,实现更好的扩展性。

消息存储机制

Snipaste_2023-11-08_14-22-42.png

消息存储整体架构

Snipaste_2023-11-08_14-23-27.png

消息存储架构图中主要有下⾯三个跟消息存储相关的⽂件构成。

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;

IndexFile(索引⽂件)

提供了⼀种可以通过key或时间区间来查询消息的⽅法。 Index⽂件的存储位置是:HOME\store\indexHOME \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(索引⽂件) 数据。

⻚缓存与内存映射

⻚缓存(PageCache)是OS对⽂件的缓存,⽤于加速对⽂件的读写。⼀般来说,程序 对⽂件进⾏顺序读写的速度⼏乎接近于内存的读写速度,主要原因就是由于OS使⽤ PageCache机制对读写访问操作进⾏了性能优化,将⼀部分的内存⽤作PageCache。 对于数据的写⼊,OS会先写⼊⾄Cache内,随后通过异步的⽅式由pdflush内核线程 将Cache内的数据刷盘⾄物理磁盘上。对于数据的读取,如果⼀次读取⽂件时出现 未命中PageCache的情况,OS从物理磁盘上访问读取⽂件的同时,会顺序对其他相 邻块的数据⽂件进⾏预读取。

Snipaste_2023-11-08_14-25-26.png

在RocketMQ中,ConsumeQueue逻辑消费队列存储的数据较少,并且是顺序读取, 在page cache机制的预读取作⽤下,Consume Queue⽂件的读性能⼏乎接近读内 存,即使在有消息堆积情况下也不会影响性能。⽽对于CommitLog消息存储的⽇志 数据⽂件来说,读取消息内容时候会产⽣较多的随机访问读取,严重影响性能。如 果选择合适的系统IO调度算法,⽐如设置调度算法为“Deadline”(此时块存储采⽤ SSD的话),随机读的性能也会有所提升。

另外,RocketMQ主要通过MappedByteBuffer对⽂件进⾏读写操作。其中,利⽤了 NIO中的FileChannel模型将磁盘上的物理⽂件直接映射到⽤户态的内存地址中(这 种Mmap的⽅式减少了传统IO将磁盘⽂件数据在操作系统内核地址空间的缓冲区和 ⽤户应⽤程序地址空间的缓冲区之间来回进⾏拷⻉的性能开销),将对⽂件的操作 转化为直接对内存地址进⾏操作,从⽽极⼤地提⾼了⽂件的读写效率(正因为需要 使⽤内存映射机制,故RocketMQ的⽂件存储都使⽤定⻓结构来存储,⽅便⼀次将整 个⽂件映射⾄内存)。

消息刷盘

Snipaste_2023-11-08_14-26-25.png

同步刷盘

如上图所示,只有在消息真正持久化⾄磁盘后RocketMQ的Broker端才会真正返回给 Producer端⼀个成功的ACK响应。同步刷盘对MQ消息可靠性来说是⼀种不错的保 障,但是性能上会有较⼤影响,⼀般适⽤于⾦融业务应⽤该模式较多。

异步刷盘

能够充分利⽤OS的PageCache的优势,只要消息写⼊PageCache即可将成功的ACK返 回给Producer端。消息刷盘采⽤后台异步线程提交的⽅式进⾏,降低了读写延迟, 提⾼了MQ的性能和吞吐量。