1.持久化的重要性
持久化在计算机系统中具有极其重要的地位,尤其是在诸如消息队列、数据库等需要保证数据完整性和可靠性的场景中。以下是持久化的重要性体现:
-
数据可靠性:持久化能够保证数据在硬件故障、系统崩溃或断电等情况发生后仍能保持完整,避免数据丢失。在RocketMQ中,消息一旦被持久化到磁盘,即使Broker服务出现异常重启,也能从磁盘恢复未被消费的消息。
-
事务一致性:对于涉及多个操作的事务处理,持久化可以作为事务提交的关键步骤,只有当数据成功写入持久化存储后,事务才能被认为已成功完成。这对于分布式事务、消息队列的事务消息等功能至关重要。
-
灾难恢复:如果系统遭遇重大故障或者灾难事件,依赖于持久化的数据可以从备份中恢复,使得业务得以迅速恢复正常运行。
-
审计需求:很多业务场景需要长期保存历史数据以满足合规性要求或进行数据分析,这都需要依赖于数据的持久化存储。
-
高可用性:持久化也是实现系统高可用的基础,通过在不同节点间复制持久化数据,可以实现数据冗余,提高系统的容错能力和整体可用性。
2.RocketMQ中几个重要的存储文件
-
CommitLog:
- CommitLog 是 RocketMQ 最核心的消息存储文件,所有生产者发送的消息都会被顺序追加到 CommitLog 文件中,采用单个大文件顺序写的方式,极大地提高了磁盘写入性能。
- 单个 CommitLog 文件大小通常是固定的,如1GB,当文件满后会自动切换到下一个文件,文件命名规则包含物理偏移量信息,方便定位消息。
-
ConsumeQueue:
- ConsumeQueue 是消费者消费消息时使用的逻辑队列,它是基于Topic和Message Queue维度构建的索引文件,每个消费队列对应一个索引文件。
- ConsumeQueue 不直接存储消息内容,而是存储指向 CommitLog 中消息的偏移量、消息大小和其他元数据,消费者通过读取 ConsumeQueue 中的索引来快速定位到 CommitLog 中的具体消息。
-
存储流程:
- 当 Broker 收到一条消息时,它首先会将消息内容写入 CommitLog 文件,并记录消息的物理偏移量。
- 同时,会在对应的 ConsumeQueue 中添加一个索引项,记录该消息在 CommitLog 中的位置信息。
- 根据配置,RocketMQ 提供异步刷盘或同步刷盘策略,确保消息已经持久化到磁盘上,防止数据丢失。
-
MMAP技术与零拷贝:
- RocketMQ 在消息存储中利用了内存映射(Memory-Mapped Files, MMAP)技术,减少数据在操作系统内核空间和用户空间之间的复制次数,实现“零拷贝”操作,进一步提升消息存储和传输效率。
总结来说,RocketMQ 的消息存储机制兼顾了高性能、顺序写优化、消息顺序消费以及灵活的消费模式,是支撑其大规模消息处理能力的重要基础架构。
----------from AI
3.存储结构图
这张图在这篇文章已经聊过,本次重点介绍一下DefaultMappedFile文件和如何写入存储消息过程。
- DefaultMappedFile文件结构
- DefaultMappedFile是对NIO中MappedFile进行了封装,加入了一些特别处理。其中包括WROTE_POSITION_UPDATER,COMMITTED_POSITION_UPDATER,FLUSHED_POSITION_UPDATER三个原子指针,以及Buffer写入,Buffer之间提交,持久化方法等,在使用TransientStorePool时会有本地Buffer来加速处理写入速度,这个会使用到COMMITTED_POSITION_UPDATER指针。
上图可以在使用TransientStorePool流程上会多出commit,下面就来分析一下消息写入已经输盘的逻辑
4.消息写入以及刷盘
1.消息写入过程
broker接受处理消息在org/apache/rocketmq/broker/processor/SendMessageProcessor类中,对应代码
public RemotingCommand sendMessage(final ChannelHandlerContext ctx,
final RemotingCommand request,
final SendMessageContext sendMessageContext,
final SendMessageRequestHeader requestHeader,
final TopicQueueMappingContext mappingContext,
final SendMessageCallback sendMessageCallback) throws RemotingCommandException {
// 省略无关代码
if (brokerController.getBrokerConfig().isAsyncSendEnable()) {
CompletableFuture<PutMessageResult> asyncPutMessageFuture;
if (sendTransactionPrepareMessage) { //这里存储的是事物性消息 line 325
asyncPutMessageFuture = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner);
} else {
//非事物消息 line 327
asyncPutMessageFuture = this.brokerController.getMessageStore().asyncPutMessage(msgInner);
}
}
最后调用的是org.apache.rocketmq.store.logfile.DefaultMappedFile#appendMessagesInner方法将消息追加到Buffer中
protected ByteBuffer appendMessageBuffer() {
this.mappedByteBufferAccessCountSinceLastSwap++;
// 如果使用TransientStorePool,新创建的文件writeBuffer不会为null,重启恢复的文件会是null
return wri