当 Broker 异常宕机时,RocketMQ 是如何通过 abort 文件和 Checkpoint 文件进行数据恢复的?
在 RocketMQ 中,Broker 的数据存储主要包含三个核心部分:CommitLog(存储真实消息数据)、ConsumeQueue(消费逻辑队列,存储消息在 CommitLog 中的偏移量)和 IndexFile(消息索引文件)。
当 Broker 异常宕机时,这三者之间极有可能出现数据不一致的情况(例如:消息写入了 CommitLog,但还没来得及构建 ConsumeQueue 或 IndexFile 就宕机了)。
RocketMQ 主要是通过 abort 文件和 checkpoint 文件 配合,来实现异常宕机后的数据恢复和一致性保证的。以下是具体的机制解析:
1. abort 文件:判断是否异常宕机的“指示器”
- 作用: 这是一个空文件,唯一的作用就是标识 Broker 上一次是否是正常关闭的。
- 工作机制:
- 启动时创建: 当 Broker 启动时,会在存储根目录(通常是
store/)下创建一个名为abort的文件。 - 正常关闭时删除: 当 Broker 正常关闭(Graceful Shutdown)时,JVM 的 ShutdownHook 会被触发,在执行完数据刷盘和资源清理后,会删除这个
abort文件。 - 异常宕机保留: 如果 Broker 是异常宕机(如断电、
kill -9、OS 崩溃),ShutdownHook 无法执行,abort文件不会被删除。
- 启动时创建: 当 Broker 启动时,会在存储根目录(通常是
- 恢复时的判断: Broker 再次重启时,首先检查存储目录下是否存在
abort文件。- 不存在: 说明上次是正常关闭,走正常恢复流程(Normal Recovery)。
- 存在: 说明上次是异常宕机,走异常恢复流程(Abnormal Recovery)。
2. checkpoint 文件:数据一致性的“基准参考点”
- 作用: 记录了 Broker 中各个核心存储文件最后一次成功刷盘(Flush)的物理时间戳。
- 包含的核心数据:
checkpoint文件主要包含三个时间戳:physicMsgTimestamp:CommitLog 最后一次刷盘的时间戳。logicsMsgTimestamp:ConsumeQueue 最后一次刷盘的时间戳。indexMsgTimestamp:IndexFile 最后一次刷盘的时间戳。
- 工作机制: Broker 运行时,会定时(默认也是刷盘时)更新这三个时间戳。因为这三者是异步落盘的,所以在任何时刻,这三个时间戳通常是不一致的(通常 CommitLog 时间戳 >= ConsumeQueue 时间戳)。
3. 异常宕机后的数据恢复流程(Abnormal Recovery)
当 Broker 重启发现 abort 文件存在时,会触发异常恢复流程。整个流程以 CommitLog 为绝对的“单点真相(Source of Truth)”,强制对齐 ConsumeQueue 和 IndexFile。
具体步骤如下:
第一步:加载 checkpoint 文件
Broker 会读取 checkpoint 文件中的三个时间戳。这三个时间戳中最小的那个时间戳(通常是 ConsumeQueue 或 IndexFile 的时间),代表着在这个时间点之前,所有的数据在三个文件中都是绝对一致且安全的。
第二步:恢复 CommitLog(确定有效的最大偏移量)
这是最关键的一步,目的是剔除 CommitLog 尾部因宕机导致的损坏或不完整的数据。
- 从后向前扫描: RocketMQ 会从最后一个 CommitLog 文件开始,尝试寻找第一个正常的文件。
- 验证消息完整性: 通过校验消息的 Magic Code(魔数)、消息长度、CRC 校验和等,来判断消息是否完整。
- 确定截断点: 一旦找到最后一条完整的消息,Broker 就会记录下这个位置的物理偏移量(
processOffset)。 - 物理截断: 将该偏移量之后的所有不完整数据直接丢弃(截断文件),确保 CommitLog 里的每一条消息都是结构完整的。
第三步:恢复 ConsumeQueue 和 IndexFile(数据对齐)
由于 CommitLog 是完整的,接下来就要让 ConsumeQueue 和 IndexFile 与之对齐。
- 对比偏移量: 扫描 ConsumeQueue 和 IndexFile。如果它们其中记录的物理偏移量(Offset)大于第二步中计算出的 CommitLog 的有效最大偏移量(
processOffset),说明这些索引是无效的(指向了被截断的 CommitLog 区域)。 - 清理无效索引: 将大于
processOffset的 ConsumeQueue 和 IndexFile 数据直接截断销毁。 - 重放补齐数据(Reput): 如果发现 ConsumeQueue 的进度落后于 CommitLog(例如宕机前消息刚写入 CommitLog,但还没分发到 ConsumeQueue),RocketMQ 会启动
ReputMessageService线程。该线程会从 ConsumeQueue 的末尾位置对应的 CommitLog 偏移量开始,重新读取 CommitLog 中的消息,并重新生成对应的 ConsumeQueue 和 IndexFile 记录。
第四步:完成恢复,删除 abort 文件
经过上述裁剪(Truncate)和重放(Reput)操作后,CommitLog、ConsumeQueue 和 IndexFile 达到了严格的数据一致性。此时,Broker 会认为存储系统已经恢复健康,允许对外提供服务,并删除当前的 abort 文件,创建新的 abort 文件,迎接新一轮的生命周期。
总结
abort文件的意义 是“定性”:告诉 Broker 刚才发生了灾难,不能用常规手段恢复,必须进行严格的数据校验。checkpoint文件的意义 是“定位”:提供了一个数据安全点的参考,虽然异常恢复主要是靠比对 CommitLog 的物理偏移量,但 Checkpoint 为判断文件损坏程度和分发状态提供了重要的时间轴依据。- 核心思想: 以 CommitLog 为主,索引文件为辅。 只要 CommitLog 数据没丢,哪怕 ConsumeQueue 和 IndexFile 全坏了,RocketMQ 也能通过重放 CommitLog 重新构建出所有的队列和索引。