前沿概述
在之前 CommitLog
的文章中,我们分析了 CommitLog
是如何被加载以及存储的。
CommitLog
里的消息是不分主题的,消费者根据当前 CommitLog
偏移,查找该主题下一个消费偏移,成本会非常高。
ConsumeQueue
因此而被设计出来,用作 CommitLog
的索引文件。
并且,只有消息被写入 ConsumeQueue
的时候,消费者才能消费到该消息。因为消息者面对的是 ConsumeQueue
。
ConsumeQueue 介绍
ConsumeQueue 文件结构
- 文件组织方式
consumeQueue
文件组织方式为topic/queueId/file
三层 其中test%TopicTest
为 topic,2
为 queueId,00000000000000000000
为 file - 子条目结构
consumeQueue
每个子条目为固定的 20 个字节,这是为了方便计算消费偏移,以及索引而设计的。 - file 特点
每个 file 都有
30w
个子条目,每个条目20 个字节。则一个 file 大约为5.7mb
ConsumeQueue 类抽象
ConsumeQueue
类可以理解为就是对存储结构 topic/queueId/file
的抽象。
然后 ConsumeQueue
有多个,被定义在 DefaultMessageStore
中。
public class ConsumeQueue {
// ...
// 每个 队列下,会有很多的 File, 所以这边是个 队列
private final MappedFileQueue mappedFileQueue;
private final String topic;
private final int queueId;
// ...
}
消息转存线程 - ReputMessageService
在概述中有提到,只有当 CommitLog
的消息被转存到 ConsumeQueue
之后,消费端才能够消费。而转存的工作,RocketMQ
交由了 ReputMessageService
来处理。
这边阐述下 ReputMessageService
是如何工作的。
该线程,每隔 1ms
执行一次以下逻辑。具体见 ReputMessageService#run
ReputMessageService
线程记录着当前转发偏移reputFromOffset
,当reputFromOffset
偏移量小于CommitLog
内存的写入偏移量(commitlog 此时未落盘),就会一直转发.- 根据
reputFromOffset
从CommitLog
中找到对应的消息, 并依次
转发给ConsumeQueue
,IndexFile
ConsumeQueue
调用write()
方法写入消息(未刷盘),供消费者拉取。
ReputMessageService
的reputFromOffset
值,会在 ReputMessageService
被初始化的时候,从多个 consumequeue
中去 commitlog
的最大偏移量。还记得 consumequeue
子条目的结构吧, commitlog offset, size, tag hashcode
。 reputFromOffset
的值就是 commitlog offset
+ size
。
具体见 DefaultMessage#start()
刷盘线程 - FlushConsumeQueueService
在讲解 ReputMessageService
时,有提到 consumequeue
只会调用 write()
方法写入消息,但是 write()
并不会将消息刷入磁盘。因此 RocketMQ
提供 FlushConsumeQueueService
线程处理 consumequeue
刷盘
FlushConsumeQueueService
每隔 1 秒
执行1次刷盘动作。
FlushConsumeQueueService
刷盘逻辑如下
- 新写入内容不足
2页
时,不刷盘 - 当
JVM
要退出时会被强制刷盘 - 当
60s
内,为执行任何刷盘,会被强制刷盘 - 遍历所有队列,依次刷盘
从刷盘流程来看,整体和 CommitLog
刷盘线程非常像。区别在于参数配置的不同
代码位置:FlushConsumeQueueService#run()
可配置刷盘参数
参数 | 默认值 | 作用 |
---|---|---|
flushIntervalConsumeQueue | 1000 毫秒 | 间隔多次时间执行强制刷盘动作 |
flushConsumeQueueLeastPages | 2 | 至少凑够 2 页,才会执行刷盘 |
flushConsumeQueueThoroughInterval | 60000 毫秒 | 间隔多久未刷盘,会被强制刷盘 |
ConsumeQueue 刷盘设计思考
前面,我们提到,只有消息被写入 ConsumeQueue
,消费端才能拉取到消息。
RocketMQ
为了让消费端尽快消费到写入的消息,在写入时,仅写入内存,并未持久化到磁盘上。
实际上,这样的设计,在需要与磁盘打交道的中间件中,都可以看到。