深入理解RocketMQ是如何做到高性能的?

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

深入理解RocketMQ是如何做到高性能的?


1、RocketMQ的核心Broker


对rocketmq稍有了解的同学,都知道它主要由4部分组成,Producer、Consumer、Broker、NameServer。


Broker作为Rocket MQ的核心,提供了强大的数据存储能力,可以把亿万级的消息存储在服务器磁盘上。它决定了生产者写入的吞吐量,决定了消息不能丢失,决定了消费者消费消息的吞吐量。


2、消息写入磁盘文件:CommitLog


当生产者的消息发送到一个Broker上的时候,它接收到了一条消息,会对这个消息做什么处理?


首先第一步,他会把这个消息直接写入磁盘上的一个日志文件,没错就是磁盘文件,它叫做CommitLog,直接顺序写入这个文件,如下图。


深入理解RocketMQ是如何做到高性能的?

图1 消息顺序写入CommitLog文件


这个CommitLog件默认大小是1G,如果消息很多的话,可能会创建很多个CommitLog件。


CommitLog文件的文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。


深入理解RocketMQ是如何做到高性能的?

图2 CommitLog文件


Broker收到消息后,直接追加到这个文件的末尾。


3、消息的偏移量写入磁盘文件:ConsumeQueue


我们知道每个Topic可能对应了多个Queue,那么这些Queue在Broker中是如何体现的呢?


其实在Broker中,每个Topic下的每个Queue都会对应一些列的ConsumeQueue文件。


深入理解RocketMQ是如何做到高性能的?

图3 Broker本地存储文件


就是在Broker磁盘上,会有下面这种格式的一些列文件:


~/store/consumequeue/{topic}/{queueId}/{fileName}


{topic}指代的就是某个Topic,{queueId}指代的就是某个MessageQueue。


然后存储在这台Broker机器上的Topic下的一个MessageQueue,他有很多的ConsumeQueue文件,这个ConsumeQueue文件里存储的是一条消息对应在CommitLog文件中的offset偏移量


这点比较重要,ConsumeQueue不存储真实的消息数据,只存消息数据在CommitLog文件中的偏移量。


假设有一个Topic,他有4个Queue,然后分布在两台Broker机器上,每台Broker机器会存储两个Queue。


此时生产者选择对其中一个Queue写入了一条消息,此时消息会发送到Broker上。


然后然后Broker会把这个消息写入CommitLog文件中,同时会把消息的偏移量写入两个ConsumeQueue中,ConsumeQueue0和ConsumeQueue1。它们分别对应着Topic里的Queue0和Queue1。


深入理解RocketMQ是如何做到高性能的?

图4 消息偏移量写入ConsumeQueue文件中


也就是说,Topic下的Queue0和Queue1就放在这个Broker机器上,而它们每个在磁盘上对应了一个ConsumeQueue文件,所以就是Queue0对应着Broker磁盘上的ConsumeQueue0,Queue1对应着磁盘上的ConsumeQueue1。


假设Queue的名字叫做:TopicOrderInfo,Queue0的id是0,Queue1的id是1,那么此时在Broker磁盘上应该有如下两个路径的文件:


~/store/consumequeue/TopicOrderInfo/0/ConsumeQueue0文件


~/store/consumequeue/TopicOrderInfo/1/ConsumeQueue1文件


深入理解RocketMQ是如何做到高性能的?

图5 ConsumeQueue本地磁盘文件


然后,当你的Broker收到一条消息写入了CommitLog之后,其实他同时会将这条消息在CommitLog中的物理位置,也就是一个文件偏移量(offset),写入到这条消息所属的Queue对应的ConsumeQueue文件中去。


ConsumeQueue文件存在的目的就是可以快速定位到消息真实的物理位置,在ConsumeQueue中存储的每条数据不只是消息在CommitLog中的offset偏移量,还包含了消息的长度,以及tag hashcode,一条数据是20个字节,每个ConsumeQueue文件保存30万条数据,所以计算下来每个文件是5.72MB。


需要注意的是每个Topic的每个Queue都对应了Broker机器上的多个ConsumeQueue文件,保存了这个MessageQueue的所有消息在CommitLog文件中的物理位置,也就是offset偏移量。


4、RocketMQ是如何提升CommitLog写入性能的?


CommitLog作为存储消息的核心所在,关乎着整个消息队列的吞吐量。那么Broker是如何提升整个过程的性能的呢?


Broker是基于OS操作系统的PageCache和顺序写两个机制,来提升写入CommitLog文件的性能的。


首先Broker是以顺序的方式将消息写入CommitLog磁盘文件的,也就是每次写入就是在文件末尾追加一条数据就可以了,对文件进行顺序写的性能要比对文件随机写的性能提升很多。


另外,消息写入CommitLog文件的时候,并不是直接写入磁盘文件的,而是先进入OS的PageCache内存缓存中,然后再由OS的后台线程选一个时间,异步化的将OS PageCache内存缓冲中的数据刷入底层的磁盘文件。

深入理解RocketMQ是如何做到高性能的?

图6 异步刷盘CommitLog文件


在采用磁盘文件顺序写+OS PageCache写入+OS异步刷盘的策略,基本上可以让消息写入CommitLog的性能接近直接写入内存,所以正是如此,才可以让Broker高吞吐的处理每秒大量的消息写入。


5、异步刷盘的利弊


很多时候鱼和熊掌不可兼得,我们充分提升性能的同时,就会牺牲一些高可用性。


如果生产者认为消息写入成功了,但是实际上那条消息此时是在Broker机器上的os cache中的,如果此时Broker直接宕机,那么是不是os cache中的这条数据就会丢失了?


所以异步刷盘的的策略下,可以让消息写入吞吐量非常高,但是可能会有数据丢失的风险。


所以rocketmq提供了同步刷盘,让使用者可选择。如果你使用同步刷盘模式的话,那么生产者发送一条消息出去,broker收到了消息,必须直接强制把这个消息刷入底层的物理磁盘文件中,然后才会返回ack给producer,此时你才知道消息写入成功了。


但是如果你强制每次消息写入都要直接进入磁盘中,必然导致每条消息写入性能急剧下降,导致消息写入吞吐量急剧下降,但是可以保证数据不会丢失。具体如何选择,还需要看你的业务场景。


6、总结


这篇文章主要讲broker最为核心的数据存储机制,希望伙伴们不只是记住,也要理解思考,为什么这样设计,自己的项目中是否有可以借鉴学习的地方。

推荐:

主流Java进阶技术(学习资料分享)

深入理解RocketMQ是如何做到高性能的?
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。“在看”支持我们吧!

原文始发于微信公众号(Java笔记虾):深入理解RocketMQ是如何做到高性能的?