Broker存储机制-学习笔记

本文深入探讨了RocketMQ Broker的核心组件,包括Broker的角色、存储目录结构、启动与停止流程。详细讲解了Broker存储机制,如CommitLog、ConsumeQueue和IndexFile的使用,以及消息的存储、索引构建和使用。还介绍了存储的高效性,如文件映射、顺序写文件和刷盘机制。最后讨论了索引的数据结构和使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Broker是RocketMQ体系中的核心组件之一,存储是Broker的核心功

能之一,决定整个RocketMQ体系的吞吐性能、可靠性和可用性。本章主要从存储角度讲解Broker的几方面内容:
● Broker存储概述。
● Broker数据存储机制。
● Broker数据索引存储机制。
● Broker过期数据删除机制。
● Broker主从数据同步机制。
● Broker关机后数据恢复机制。
6.1 Broker概述
本章主要讲解了 Broker的基本知识,以及 Broker在体系中所处的 地位。带领读者从部署结果上看Broker的各个文件目录结构,为下一章学习存储模块打下基础。
本章的核心内容有:
● Broker在RocketMQ体系中所处的地位。
● Broker的数据目录结构。
● Broker的启动和停止流程。
6.1.1 什么是Broker
Broker是RocketMQ中核心的模块之一,主要负责处理各种TCP请求 (计算)和存储消息(存储),在各个组件中的角色如图6-1所示。
Broker分为Master和Slave。Master主要提供服务,Slave在Master 宕机后提供消费服务。
6.1.2 Broker存储目录结构
Broker在安装启动后会自动生成若干存储文件,文件内容如图6-2 所示。

Commitlog:这是一个目录,其中包含具体的commitlog文件。文件名长度为20个字符,文件名由该文件保存消息的最大物理offset值在 高 位 补 0 组 成 。 每个文件大小 一 般 是 1GB ,可 以 通 过 mapedFileSizeCommitLog进行配置。
consumequeue:这是一个目录,包含该 Broker 上所有的 Topic 对 应 的 消 费 队 列 文 件 信 息。 消 费 队 列 文 件 的 格 式 为 “./consumequeue/Topic名字/queue id/具体消费队列文件”。每个消
费队列其实是commitlog的一个索引,提供给消费者做拉取消息、更新 位点使用。
Index:这是一个目录,全部的文件都是按照消息key创建的Hash索引。文件名是用创建时的时间戳命名的。
Config:这是一个目录,保存了当前Broker中全部的Topic、订阅关系和消费进度。这些数据Broker会定时从内存持久化到磁盘,以便宕机后恢复。
abort:Broker 是否异常关闭的标志。正常关闭时该文件会被删除,异常关闭时则不会。当Broker重新启动时,根据是否异常宕机决定是否需要重新构建Index索引等操作。
checkpoint:Broker最近一次正常运行时的状态,比如最后一次正常刷盘的时间、最后一次正确索引的时间等。

6.1.3 Broker启动和停止流程
RocketMQ设计遵从简单、高效的原则。虽然整套消息队列比较复杂,但是RocketMQ从启动/停止流程上看,非常简单易懂。Broker的启动流程如图6-3所示。
下面对图6-3中的启动组件进行讲解:
启 动 命 令 分 为 两 个 脚 本 : ./bin/mqbroker 和./bin/runbroker.sh。mqbroker 准备了RocketMQ 启动本身的环境数据,比如 ROCKETMQ_HOME 环境变量。
runbroker.sh 主要设置了JVM启动参数,比如JAVA_HOME、Xms、Xmx。 对于以上两个启动脚本,RocketMQ 开发人员曾经做过无数次测试,一般情况下可以直接使用。此外,RocketMQ开发人员修改了一些Linux系统参数,以保证Broker可以运行在最适合、最高效的环境中, 所有的系统参数配置都在./bin/os.sh脚本中。
如何使用该脚本呢?在该脚本中找到 su-admin-c ′ulimit-n′,将 admin 修改为RocketMQ启动账号,在启动RocketMQ之前执行该脚本即可。

BrokerStartup.java类主要负责为真正的启动过程做准备,解析脚 本传过来的参数,初始化Broker配置,创建BrokerController实例等工 作。BrokerController.java 类 是 Broker 的 掌 控 者 , 它 管 理 和 控 制

Broker的各个模块,包含通信模块、存储模块、索引模块、定时任务 等。在 BrokerController 全部模块初始化并启动成功后,将在日志中 输出info信息“boot success”。
在对图6-3的启动组件基本了解后,我们来讲一下这些组件是如何 启动RocketMQ的。具体启动过程分为以下几个步骤:
第一步:初始化启动环境。 这是由./bin/mqbroker 和./bin/runbroker.sh 两个脚本来完成
的。/bin/mqbroker 脚本主要用于设置 RocketMQ 根目录环境变量,调 用./bin/runbroker.sh 进入 RocketMQ 的启动入口,核心代码如下:

./bin/runbroker.sh 脚本的主要功能是检测 JDK 的环境配置和 JVM 的参数配置。JDK的环境配置的检查逻辑的实现代码如下:

下面讲一下 JVM 的参数配置。如下代码所示,通常,-Xms、- Xmx、-Xmn、-XX:MaxDirectMemorySize这4个参数会随着部署RocketMQ 服务器的物理内存大小的变化而进行相应的改变。

第二步:初始化BrokerController。
该初始化主要包含RocketMQ启动命令行参数解析、Broker各个模块 配置参数解析、Broker各个模块初始化、进程关机Hook初始化等过程, 接下来我们逐一进行讲解。
RocketMQ 启 动 命 令 行 参 数 解 析 。 其 代 码 在 org.apache.rocketmq.broker.BrokerStartup.createBrokerController()方法中。RocketMQ的启动参数支持启动命令指定,也可以在配置文件中进行配置。通常,命令行参数的优先级大于配置文件。 命令行参数是如何在createBrokerController()方法中解析的呢?代码实现如下:

 

通过第三方库将命令行输入参数解析为commandLine对象,再获取 输入参数值。 命令行参数的启动比较简单,如果大量的RocketMQ配置项放在启动命令中,就会导致启动命令较长,难以维护,一般推荐启动RocketMQ使 用配置文件的方式。配置文件在createBrokerController()方法中被
解析的代码如下:
注意,以上代码中加粗的部分是 RocketMQ 配置文件加载及初始化 的具体实现。MixAll.properties2Object()方法的主要功能是,按照 properties中配置的 key与目标对象字段名是否相同来设置对应的值。 在 brokerConfig 、 nettyServerConfig 、 nettyClientConfig 、
messageStoreConfig这些基本配置对象初始化完毕后,还有后续代码依 据各种启动条件重新调整部分参数。在 各 个 配 置 对 象 初 始 化 完 毕 后 , 程 序 会 调 用 BrokerController.initialize()方法对 Broker的各个模块进行初始 化。 首先,加载Broker基础数据配置和存储层服务,核心代码如下:

 

上面代码中的 xxxxConfigManager.load()方法的功能是加载 Broker 基础数据配置,包含 Broker 中的 Topic、消费位点、订阅关系、消费过滤(无实际数据需要加载)。这些配置加载成功后,初始化存储层服务对象messageStore和Broker监控统计对象brokerStats。
然后,Broker会初始化通信层服务和一系列定时任务。通信层服务主要初始化正常通信通道、VIP通信通道和通信线程池。由于代码太多,并且大多数逻辑是相似的,所以这里以VIP通道为例,讲解通信层 服务初始化;以消费进度定时持久化为例,讲解定时任务初始化。 我们知道在Broker中存在VIP通道,通信端口是10909,与正常通信端口10911相差2,这两个端口有什么关系吗?VIP 通道又是如何初始化的呢?
fastConfig 就是 VIP通信层的配置,其配置对象“克隆”自正常通 信 的 配 置 对 象 , 唯 独 通 信 端 口 是 nettyServerConfig.getListenPort()-2,也就是 10911-2。利用 fastConfig初始化fastRemotingServer的结果也就是我们常用的VIP通道。 从fastConfig和fastRemotingServer的实现类命名来看,我们知道 RocketMQ的通信层实现本质上是基于Netty的,那么通信层又是如何处
理客户端发送的Netty请求的呢? 通信层对象初始化完成后,会调用 this.registerProcessor() 方法,这里将正常的通信层对象和VIP通道的通信层对象与各个请求处 理器进行关联,比如将发送消息的请求交给接收消息的请求处理器进行处理,相关实现代码如下:

 

在对VIP通信层初始化有了基本的了解后,下面介绍消费进度定时持久化。
Broker在接收消费者上报的消费进度后,会定期持久化到物理文件中,当消费者因为重新发布或者宕机而重启时,能从消费进度中得知恢复,不至于重复消费。定期持久化任务的初始化代码如下:

 

从 以 上 代 码 中 知 道 , 持 久 化 周 期 可 以 通 过 参 数 flushConsumerOffsetInterval(以ms为单位)进行配置。
第三步:启动RocketMQ的各个组件。 组 件 启 动 代 码 在 org.apache.rocketmq.broker.BrokerController.start()方法中, 由于启动过程非常复杂,笔者按照代码执行顺序,主要讲解启动的各个 模块功能,对于详细启动过程,读者可以自行查看源码。
this.messageStore : 存 储 层 服 务 , 比 如 CommitLog 、 ConsumeQueue存储管理。
this.remotingServer:普通通道请求处理服务。一般的请求都是在这里被处理的。
this.fastRemotingServer:VIP 通道请求处理服务。如果普通通 道比较忙,那么可以使用VIP通道,一般作为客户端降级使用。
this.brokerOuterAPI:Broker访问对外接口的封装对象。
this.pullRequestHoldService:Pull长轮询服务。
this.clientHousekeepingService:清理心跳超时的生产者、消 费者、过滤服务器。
this.filterServerManager:过滤服务器管理。
下面我们将Broker信息注册到Namesrv,并处理Master与Slave的关系,具体代码如下:

this.brokerStatsManager:Broker监控数据统计管理。

this.brokerFastFailure:Broker快速失败处理。
以上模块全部启动成功后,Broker就启动成功了。
在了解了RocketMQ的启动进程后,关闭Broker进程其实是启动过程的逆过程,详细过程就不再单独讲解,笔者总结了Broker关闭进程的过程,如图6-4所示。
Broker 关闭只是调用 BrokerStartup.java 中注册 JVM Hook 的
BrokerController.shutdown()方法,该方法再调用各个模块关闭方法,最后关闭整个进程。
Broker 进程关闭处理完成后,日志输出info 信息“Shutdown hook over”。

 

6.2 Broker存储机制
堆积能力是消息队列的一个重要考核指标。存储机制是RocketMQ中的核心,也是亮点设计,因为存储机制决定写入和查询的效率。 阅读本章需要以下前置知识点:
● Java NIO.
● 程序可以使用的全部内存=物理内存+虚拟内存。
● 操作系统Page Cache机制(页缓存机制)。
本章的核心讲解内容如下:
● RocketMQ存储结构。
● RocketMQ存储核心——文件映射和顺序写文件。
● RocketMQ刷盘过程。
6.2.1 Broker消息存储结构
1.Broker存储概述
从上一节中我们了解到,Broker通过 CommitLog、ConsumeQueue、 IndexFile等来组织存储消息,下面介绍消息存储文件CommitLog。org.apache.rocketmq.store.CommitLog类负责处理全部消息的存储逻辑——普通消息、定时消息、顺序消息、未消费的消息和已消费的消息。消息的保存结构如表6-2所示。

 

Version:0.9 StartHTML:0000000105 EndHTML:0000000861 StartFragment:0000000141 EndFragment:0000000821
我们得知CommitLog目录下有多个CommitLog文件。其实 CommitLog只有一个文件,为了方便保存和读写被切分为多个子文件, 所有的子文件通过其保存的第一个和最后一个消息的物理位点进行连接

 

Version:0.9 StartHTML:0000000105 EndHTML:0000000516 StartFragment:0000000141 EndFragment:0000000476 Broker按照时间和物理的offset顺序写CommitLog文件,每次写的
时候需要加锁,文件代码如下:

 Version:0.9 StartHTML:0000000105 EndHTML:0000001212 StartFragment:0000000141 EndFragment:0000001172

每 个 CommitLog 子 文 件 的 大 小 默 认 是 1GB(1024×1024×1024B),可以通过mapedFileSizeCommitLog进行配 置。当一个CommitLog写满后,创建一个新的CommitLog,继续上一个 ComiitLog的Offset写操作,直到写满换成下一个文件。所有CommitLog 子文件之间的Offset是连续的,所以最后一个CommitLog总是被写入的。

2.为什么写文件这么快
RocketMQ的存储设计中,很大一部分是基于 Kafka的设计进行优化的,这里我们非常感谢Kafka的设计和开发人员,有了你们才成就了今 天的RocketMQ。
RocketMQ是基于Java编写的消息中间件,支持万亿级的消息扭转和保存,RocketMQ写文件为什么会这么快呢?我们先来了解以下名词:
Page Cache:现代操作系统内核被设计为按照Page读取文件,每个Page默认为4KB。因为程序一般符合局部性原理,所以操作系统在读取一段文件内容时,会将该段内容和附件的文件内容都读取到内核Page中(预读),下次读取的内容如果命中Page Cache就可以直接返回内容,不用再次读取磁盘,过程如图6-6所示。
Page Cache机制也不是完全无缺点的,当遇到操作系统进行脏页回写、内存回收、内存交换等情况时,就会引起较大的消息读写延迟。对于这些情况,RocketMQ 采用了多种优化技术,比如内存预分配、文件预热、mlock系统调用等,以保证在最大限度地发挥Page Cache 机制的 优点的同时,尽可能地减少消息读写延迟。所以在生产环境部署RocketMq的时候,尽量采用SSD独享磁盘,这样可以最大限度地保证读写性能。
Virtual Memory(虚拟内存):为了保证每个程序有足够的运行空间和编程空间,可以将一些暂时不用的内存数据保存到交换区(其实是磁盘)中,这样就可以运行更多的程序,这种“内存”被称为虚拟内存(因为不是真的内存)。
操作系统的可分配内存大小=虚拟内存大小+物理内存大小。 零拷贝和 Java 文件映射:从文件读取流程可以看到,读取到内 核态的数据会经历两次拷贝,第一次从内核态内存拷贝到用户态内存,
第二次从用户态内存拷贝到 Java 进程的某个变量地址,这样Java变量 才能读取数据,如图6-7所示。 为了提高读写文件的效率,IBM实现了零拷贝技术,它是世界上最早实现该技术的公司,后来各个厂商(如甲骨文等)也纷纷实现了该技术。

java.nio.MappedByteBuffer.java文件中实现了零拷贝技术,即 Java进程映射到内核态内存,原来内核态内存与用户态内存的互相拷贝过程就消失了。
在消息系统中,用户关心的往往都是最新的数据,理论上,基本的操作都在Page Cacge中,Page Cache的操作速度和内存基本持平,所以速度非常快。当然,也存在读取历史消息而历史消息不在Page Cache中的情况,比如在流处理和批处理中,经常将消费重置到历史消息位点, 以重新计算全部结果。这种情况只是在第一次拉取消息时会读取磁盘,以后可以利用磁盘预读,几乎可以做到不再直接读取磁盘,其性能与利用Page Cache相比,只在第一次有差异。
6.2.2 Broker消息存储机制
1.Broker消息存储的流程通过6.1.2节的介绍我们知道,RocketMQ使用CommitLog文件将消息
存储到磁盘上,那么RocketMQ存储消息到磁盘的过程是怎样的呢?
RocketMQ首先将消息数据写入操作系统Page Cache,然后定时将数据刷入磁盘。下面主要介绍RocketMQ是如何接收发送消息请求并将消息写入Page Cache的,整个过程如图6-8所示。
(1)Broker接收客户端发送消息的请求并做预处理.SendMessageProcessor.processRequest()方法会自动被调用接收、解析客户端请求为消息实例。该方法执行分为四个过程:解析请求参数、执行发送处理前的 Hook、调用保存方法存储消息、执行发送处理后的Hook。 随着RocketMQ版本的迭代更新,通信层的协议也出现了不兼容的变化,比如解析请求参数需要根据不同的客户端请求协议版本做不同处理。 发送消息在RocketMQ 4.2.0中支持发送单个消息和批量消息,二者处理逻辑相似,在此笔者以发送单个消息为例进行讲解。
(2)Broker存储前预处理消息。 预 处 理 方 法 为
org.apache.rocketmq.broker.processor.SendMessageProcessor.sendMessage()。 首先,设置请求处理返回对象标志,处理代码如下:
Netty是异步执行的,也就是说,请求发送到Broker被处理后,返 回结果时,在客户端的处理线程已经不再是发送请求的线程,那么客户端如何确定返回结果对应哪个请求呢?很简单,我们可以通过返回标志来判断。
其次,做一系列存储前发送请求的数据检查,比如死信消息处理、 Broker是否拒绝事务消息处理、消息基本检查等。消息基本检查方法为 AbstractSendMessageProcessor.msgCheck(),该方法的主要功能如
下:
● 校验Broker是否配置可写。
● 校验Topic名字是否为默认值。
● 校验Topic配置是否存在。
● 校验queueId与读写队列数是否匹配。
● 校验Broker是否支持事务消息(msgCheck之后进行的校验)。
(3)执行DefaultMessageStore.putMessage()方法进行消息校验和存储模块检查。
在真正保存消息前,会对消息数据做基本检查、对存储服务做可用性检查、对Broker做是否Slave的检查等,总结如下:
● 校验存储模块是否已经关闭。
● 校验Broker是否是Slave。● 校验存储模块运行标记。
● 校验Topic长度。
● 校验扩展信息的长度。
● 校验操作系统Page Cache是否繁忙。具体实现代码如下:

 

以上代码说明如下:
begin:CommitLog加锁开始时间,写CommitLog成功后,该值为0。
diff:当前时间和CommitLog持有锁时间的差值。
如果 isOSPageCacheBusy()方法返回 true,则表示当前有消息正在写入 CommitLog,并且持有锁的时间超过设置的阈值。
( 4 ) 执 行
org.apache.rocketmq.store.CommitLog.putMessage()方法,将消息写入CommitLog。存储消息的核心处理过程如下:
● 设置消息保存时间为当前时间戳,设置消息完整性校验码CRC(循环冗余码)。
● 延迟消息处理。如果发送的消息是延迟消息,这里会单独设置
延迟消息的数据字段,比如修改Topic为延迟消息特有的Topic——
SCHEDULE_TOPIC_XXXX,并且备份原来的Topic和queueId,以便延迟消 息在投递后被消费者消费。
延迟消息的处理代码如下:

 

● 获取最后一个 CommitLog 文件实例 MappedFile,锁住该 MappedFile 。 默 认 为 自 旋 锁 , 也 可 以 通 过 useReentrantLockWhenPutMessage 进 行 配 置 、 修 改 和 使 用 ReentrantLock。
● 校验最后一个 MappedFile,如果结果为空或已写满,则新创建一个 MappedFile返回。● 调 用 MappedFile.appendMessage ( final MessageExtBrokerInner msg,final AppendMessageCallback cb), 将消息写入MappedFile。
根 据 消 息 是 单 个 消 息 还 是 批 量 消 息 来 调 用 AppendMessageCallback.doAppend ( ) 方 法 , 并 将 消 息 写 入 Page Cache,该方法的功能包含以下几点:
(1)查找即将写入的消息物理Offset。
(2)事务消息单独处理。这里主要处理Prepared类型和Rollback 类型的消息,设置消息queueOffset为0。
(3)序列化消息,并将序列化结果保存到ByteBuffer中(文件内存映射的Page Cache或 Direct Memory,简称 DM)。特别地,如果将刷盘设置为异步刷盘,那么ransientStorePoolEnable=true时,会先写入DM,DM中的数据再异步写入文件内存映射的Page Cache中。因为消费者始终是从Page Cache中读取消息消费的,所以这个机制也称为“读写分离”。
(4)更新消息所在Queue的位点,具体实现代码如下:

 

 
以上代码中,CommitLog.this.TopicQueueTable类型是HashMap< String/*topic-queueid*/ ,Long/*offset*/> ,
CommitLog.this.TopicQueueTable的 key是 Topic名字与消息所在 Queue的Queue Id的构成,value是消息位点值。

在消息存储完成后,会处理刷盘逻辑和主从同步逻辑,分别调用
org.apache.rocketmq.store.CommitLog.handleDiskFlush()方法和
org.apache.rocketmq.store.CommitLog.handleHA()方法。具体代码不再详细讲解。
在Broker处理发送消息请求时,由于处理器SendMessageProcessor 本身是一个线程池服务,所以设计了快速失败逻辑,方便在高峰时自我保 护 。 实 现 代 码 在
org.apache.rocketmq.broker.latency.BrokerFastFailure.cleanExpi redRequest()方法中。
在 BrokerController 启动 BrokerFastFailure 服务时,会启动一个定时任务处理快速失败的异常,启动及扫描代码如下:
从 以 上 代 码 可 以 看 到 , 每 间 隔 10ms 会 执 行 一 次 cleanExpiredRequest()方法,清理一些非法、过期的请求,具体有 如下3种处理方式:第一种,系统繁忙时发送消息请求快速失败处理,具体代码如下:

当操作系统Page Cache繁忙时,会将发送消息请求从发送消息请求线程池工作队列中取出来,直接返回SYSTEM_BUSY。如果此种情况持续发生说明系统已经不堪重负,需要增加系统资源或者扩容来减轻当前Broker的压力。
第二种,发送请求超时处理。
第三种,拉取消息请求超时处理。
第二种和第三种的代码逻辑与第一种代码逻辑的处理类似,如果出现了,说明请求在线程池的工作队列中排队时间超过预期配置的时间, 那么增加排队等待时间即可。如果请求持续超时,说明系统可能达到瓶颈,那么需要增加系统资源或者扩容。
2.内存映射机制与高效写磁盘
在了解了 RocketMQ 存储消息的过程后,读者应该还想知道, RocketMQ 是如何保证高效存储的呢?RocketMQ在存储设计中通过内存映射、顺序写文件等方式实现了高吞吐——这些具体是怎么实现的呢?
下面介绍一些RocketMQ的基本数据结构。
org.apache.rocketmq.store.CommitLog:RocketMQ 对存储消息的物理文件的抽象实现,也就是物理CommitLog文件的具体实现。
org.apache.rocketmq.store.MappedFile:CommitLog文件在内存中的映射文件,映射文件同时具有内存的写入速度和与磁盘一样可靠的持久化方式。
org.apache.rocketmq.store.MappedFileQueue:映射文件队列中有全部的CommitLog映射文件,第一个映射文件为最先过期的文件, 最后一个文件是最后过期的文件,最新的消息总是写入最后一个映射文件。
CommitLog、MappedFileQueue、MappedFile与物理CommitLog文件的关系如图6-9所示。

 

每个 MappedFileQueue 包含多个 MappedFile,就是真实的物理 CommitLog 文件。在Java 中通过 java.nio.MappedByteBuffer 来实现文件的内存映射,即文件读写都是通过MappedByteBuffer(其实是Page Cache)来操作的。
写入数据时先加锁,然后通过Append方式写入最新MappedFile。对于读取消息,大部分情况下用户只关心最新数据,而这些数据都在PageCache中,也就是说,读写文件就是在Page Cache中进行的,其速度几乎等于直接操作内存的速度。
3.文件刷盘机制
消息存储完成后,会被操作系统持久化到磁盘,也就是刷盘。
RocketMQ 支 持 两 种 刷 盘 方 式 , 在 Broker 启 动 时 配 置
flushDiskType=SYNC_FLUSH 表 示 同 步 刷 盘 , 配 置
flushDiskType=ASYNC_FLUSH表示异步刷盘。
刷盘涉及以下3个线程服务,类图继承关系如图6-10所示。

 

GroupCommitService 就 是
org.apache.rocketmq.store.CommitLog.GroupCommitService——同步刷盘服务。在Broker存储消息到Page Cache后,同步将Page Cache刷到磁盘,再返回客户端消息并写入结果

 

FlushRealTimeService 就 是 org.apache.rocketmq.store.CommitLog.FlushRealTimeService——异 步刷盘服务。在Broker存储消息到Page Cache后,立即返回客户端写入 结果,然后异步刷盘服务将Page Cache异步刷到磁盘

 

CommitRealTimeService 就 是 org.apache.rocketmq.store.CommitLog.CommitRealTimeService—— 异步转存服务。Broker通过配置读写分离将消息写入直接内存(Direct
Memory,简称 DM),然后通过异步转存服务,将 DM 中的数据再次存
储到 Page Cache中,以供异步刷盘服务将Page Cache刷到磁盘中

 

 

 

 

下面对org.apache.rocketmq.store.MappedFile.commit0()方法
中的核心变量作如下说明:wrotePosition:DM中已写入的消息位置。
committedPosition:已经转存的消息位置。
writeBuffer:配置 Broker 读写分离后,当存储消息流传到
ByteBuffer 时,会优先写入writeBuffer(实际是DM,不是真正的Page Cache,也可以叫作内存缓冲区)。
fileChannel:CommitLog映射文件的读写通道。
org.apache.rocketmq.store.MappedFile.commit0()方法的作用 就 是 将 writeBuffer ( DM ) 中 的 数 据 读 取 出 来 , 写 入 fileChannel(CommitLog映射文件)。
3)转存失败,唤醒异步刷盘线程。转存数据失败,并不代表没有数据被转存到Page Cache中,而是说明有部分数据转存成功,部分数据转存失败。所以可以唤醒刷盘线程执行刷盘操作。而如果转存成功, 则正常进行异步刷盘即可。
在异步转存服务和存储服务把消息写入Page Cache后,由异步刷盘服务将消息刷入磁盘中,过程如图6-17所示。
异步刷盘服务的主要功能是将Page Cache中的数据异步刷入磁盘, 并 记 录 Checkpoint 信 息 。 异 步 刷 盘 的 实 现 代 码 主 要 在 org.apache.rocketmq.store.CommitLog.FlushReal TimeService.run()方法中,下面将分步骤进行讲解。

 

 

 

下面对代码中的核心变量做如下说明:
flushCommitLogTimed:是否定时刷盘,设置为True表示定时刷盘;
设置为False表示实时刷盘。默认为False,即实时刷盘。
interval:在Broker中配置项名是flushIntervalCommitLog,刷盘间隔默认为500ms。
flushPhysicQueueLeastPages:每次刷盘的页数,默认为4页。
flushPhysicQueueThoroughInterval:两次刷盘操作的最长间隔时间,默认为10s。
当 前 刷 盘 操 作 距 离 上 次 刷 盘 时 间 大 于 flushPhysicQueueThoroughInterval 时 , 设 置flushPhysicQueueLeastPages=0,表示继续将上次未完成的数据进行刷 盘。
第二步,等待刷盘间隔。Broker是如何实现定时和实时刷盘的呢?
具体实现代码如下:

 

 

 

 

 

 

 

6.3 Broker CommitLog索引机制
绝大部分存储组件都有索引机制,RocketMQ 也一样,有巨量堆积能力的同时,通过索引可以加快读取和查询。
本节主要讲解RocketMQ的ConsumeQueue和IndexFile两种索引的基本原理:
● 索引的数据结构。
● 索引的构建过程。
● 索引如何使用。
6.3.1 索引的数据结构
索引,是为增加查询速度而设计的一种数据结构。在RocketMQ中也是以文件形式保存在Broker中的。
从6.1.1节中我们得知,Broker中有 两种索引:Consumer Queue和Index File。
第一种,Consumer Queue:消费队列,主要用于消费拉取消息、更 新 消 费 位 点 等 所 用 的 索 引 。
源 代 码 可 以 参 考 文 件 org.apache.rocketmq.store.ConsumeQueue,如图6-20所示,文件内保
存了消息的物理位点、消息体大小、消息Tag的Hash值。

 

 

 

1.创建Consume Queue和Index File Consume Queue 和 Index File 两 个 索 引 都 是 由
org.apache.rocketmq.store.ReputMessage Service类创建的,该类的类图关系如图6-23所示。
从图 6-23 可知,ReputMessageService 是一个后台线程服务,启动 和 初 始 化 都 是 在 存 储 模 块 类 org.apache.rocketmq.store.DefaultMessageStore的构造函数中完成 的。

 

6.3.3 索引如何使用

1.按照位点查消息RocketMQ支持Pull和Push两种消费模式,Push模式是基于Pull模式 的,两种模式都是通过拉取消息进行消费和提交位点的。这里我们主要讲 Broker 在处理客户端拉取消息请求时是怎么查询消息的,先来看一下Broker的实现代码:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值