刷盘机制
生产者发布MQ给Brocker,Brocker在存储这些数据的时候,需要进行刷盘,分为同步刷盘和异步刷盘。
在同步刷盘中需要等待一个刷盘成功的ACK,同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般地适用于金融等特定业务场景。
而异步刷盘往往是开启一个线程去异步地执行刷盘操作。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量,一般适用于如发验证码等对于消息保证要求不太高的业务场景。
一般地,异步刷盘只有在Broker意外宕机的时候会丢失部分数据,你可以设置Broker的参数FlushDiskType来调整你的刷盘策略(ASYNC_FLUSH或者SYNC_FLUSH)
同步复制和异步复制
上面的同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的Borker主从模式下,主节点返回消息给客户端的时候是否需要同步从节点。
- 同步复制:也叫"同步双写",也就是说,只有消息同步双写到主从节点上时才返回写入成功。
- 异步复制:消息写入主节点之后就直接返回写入成功。
然而,很多事情是没有完美的方案的,就比如我们进行消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,所以需要程序员根据特定业务场景去选择适应的主从复制方案。
异步同步复制策略,不仅影响消息的可靠性,还影响到了这个Brocker的可用性。
存储机制
首先想大家介绍RocketMQ消息存储架构中的三大角色: CommitLog、ConsumeQueue 和 IndexFile。
- CommitLog:消息主体以及元数据的存储主体,存储 Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G,记录了消息内容以及大小,当第一个文件写满了,消息主要是顺序写入日志文件,当文件满了,写入下一个文件。
- ConsumeQueue:消息消费队列,引入的目的主要是提高消息消费的性能,由于RocketMQ 是基于主题 Topic的订阅模式,消息消费是针对主题进行的,如果要遍历 Commitlog文件中根据Topic检索消息是非常低效的。Consumer即可根据 ConsumeQueue 来查找待消费的消息。其中,ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量 offset,消息大小size和消息息Tag的HashCode值。consumequeue文件可以看成是基于 topic 的 commitlog索引文件。
- IndexFile:IndexFile (索引文件)提供了一种可以通过key或时间区间来查询消息的方法。
总结来说,整个消息存储的结构,最主要的就是CommitLoq和ConsumeQueue。而ConsumeQueue你可以大概理解为Topic中的队列。
RocketMQ 采用的是混合型的存储结构。即为 Broker单个实列下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 Kafka中会为每个Topic分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,RocketMQ是不分书的种类直接成批的塞上去的,而Kafka分类区域的,是将书本放入指定的。这也是和Kafka最大的区别,也是为什么Kafka建议在少Topic,单TopicQPS大的场景下使用。
RocketMQ为什么要这么做呢?原因是提高数据的写入效率,不分Topic意味着我们有更大的几率获取成批的消息进行数据写入,但也会带来一个麻烦就是读取消息的时候需要遍历整个大文件,这是非常耗时的。
所以,在RocketMQ中又使用了 ConsumeQueue 作为每个队列的索引文件来提升读取消息的效率。我们可以直接根据队列的消息序号,计算出索引的全局位置,然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。
生产者发送消息会指定 Topic 、QueueId和具体消息内容,而在 Broker中管你是哪门子消息,他直接全部顺序存储到了CommitLog。而根据生产者指定的Topic 、 QueueId、这条消息本身在 CommitLog的偏移(offset),消息本身大小、和tag的hash值存入对应Topic的 ConsumeQueuue索引文件中。而在每个队列中都保存了Consume0ffset 即每个消费者组的消费位置,消费者拉取消息进行消费的时候只需要根据 Consume0ffset在ConsumeQueuue文件中获取下一个未被消费的消息就行了。