AutoMQ 中的元数据管理

本文所述 AutoMQ 的元数据管理机制均基于 AutoMQ Release 1.1.0 版本 [1]。

01

前言

AutoMQ 作为新一代基于云原生理念重新设计的 Apache Kafka 发行版,其底层存储从传统的本地磁盘替换成了以对象存储为主的共享存储服务。对象存储为 AutoMQ 带来可观成本优势的同时,其与传统本地磁盘的接口和计费方式的差异也为 AutoMQ 在实现上带来了挑战,为解决这一问题,AutoMQ 基于 KRaft 进行拓展,实现了一套针对对象存储环境的流存储元数据管理机制,在兼顾成本的同时,极大的保证了基于对象存储的读写性能。

02

AutoMQ 需要哪些元数据

KV 元数据

在之前的文章中(AutoMQ 如何做到 Apache Kafka 100% 协议兼容 [2]),我们介绍过了 AutoMQ 的存储层如何基于 S3Stream [3] 实现对对象存储服务的流式读写的,每个分区都有与之对应的多个 Stream 来分别负责存储分区的元数据、消息、Time Index、Txn Index 等。AutoMQ 通过 KV 元数据来保存分区所对应的 MetaStream 的 StreamId,从而保证分区在不同节点打开时都能正确获得与 Stream 的映射关系。

Stream 元数据

由于分区和 Stream 有着一对多的映射关系,当分区发生迁移、数据写入、位点 Trim 等事件时,其对应的 Stream 状态也会相应发生变化。因此 AutoMQ 对每个 Stream 都维护了相应的元数据,主要由以下部分组成:

  • Stream Epoch:当分区发生迁移时,会提升对应的 Stream Epoch,后续所有对 Stream 的操作都需要对 Epoch 进行检查,保证只有 Stream 当前所在节点能够对 Stream 进行操作

  • Start Offset:用于表示 Stream 的起始位点,当分区发生 Trim 时,其对应的 Stream 的起始位点也会被相应更新

  • End Offset:用于表示 Stream 的最大位点,当分区消息成功写入并提交后,Stream 的最大位点也会相应推进

  • Ranges:随着分区的迁移,Stream 也会在不同的节点上产生数据,Ranges 保存着 Stream 在各个节点打开期间的位点变化,在后文中会具体介绍 Ranges 的作用

  • StreamObjects:用于保存 Stream 对应的 StreamObject 的 ObjectId,以及在相应 Object 上的位点范围

每当 Controller 接收到 Stream 的相关操作时(如 create, open, commit, trim 等),都会产生相应的 S3StreamRecord,通过 KRaft 层持久化后将状态更新到内存中,并同步更新到各个 Broker 的元数据缓存。

Node 元数据

Node 元数据由以下部分组成:

  • Node Id:即节点 Id

  • Node Epoch:即节点 Epoch,与 Stream Epoch 作用类似,当节点发生重启时会提升相应的 Node Epoch,来保证只有带有最新 Epoch 的节点能够进行 Stream 相关操作

  • Failover Mode:用于标识当前节点是否处在 Failover 模式(关于 AutoMQ 的 Failover 能力会在后续的文章中介绍)

  • StreamSetObjects:用于保存当前节点产生的各个 StreamSetObject,以及各 Object 上不同 Stream 的位点索引信息

其中, Node Epoch 及 Failover Mode 会在节点首次启动时,通过 open streams 接口产生一条 NodeWALMetadataRecord 来进行更新,而 StreamSetObjects 则会在节点向 Controller 提交 StreamSetObject 时通过 S3StreamSetObjectRecord 更新。

Object 元数据

Object 元数据负责所有对象存储对象的生命周期管理,包括对象的状态、大小、Key、过期时间、提交时间、标记删除时间等。

03

整体流程

本节将介绍 AutoMQ 如何在各个阶段利用上述介绍的元数据实现对对象存储的高效利用。

分区打开

分区打开时,节点会先向 Controller 请求该分区对应的 MetaStream Id,若 MetaStream 不存在,则表示该分区为首次创建,此时节点会为该分区创建一个 MetaStream 并将分区到 MetaStream 的映射关系发送给 Controller,Controller 收到后根据 Key 和 Value 创建出 KVRecord,通过 KRaft 层持久化后将 KV 映射关系写入内存中。若 MetaStream 存在,则从 MetaStream 中读取出分区各 Segment 对应的 Stream 的信息,从而能够使得后续对分区的读写能够正确转换为对 Stream 的读写。

分区数据写入

所有针对分区的写入通过上述 MetaStream 中解析出的映射关系,最终都会转变为对 Stream 的写入,而所有 Stream 写入的数据都会首先写入 EBS WAL,持久化成功后直接向上层返回结果;同时写入的数据会继续在内存中进行混合攒批,直到攒批大小超过阈值时触发上传。上传触发后,节点会遍历此次攒批的数据中的各个 Stream,将连续数据段超过一定阈值大小的上传为 StreamObject,剩余数据上传为 StreamSetObject。每个 Object 上传时,节点会先向 Controller 申请全局唯一的 Object Id,此时 Controller 的 Object 元数据中会记录下该 Object 的状态和过期时间,节点通过 Object Id 生成对象存储的写入路径并上传数据。当本次上传任务产生的所有 Object 全部写入完成后,节点会向 Controller 发起 Commit 请求,Controller 将产生一系列 KRaft Record 更新元数据:

  • Object 元数据:将此次提交的 Object 状态从 PREPARED 置为 COMMITTED

  • Stream 元数据:推进此次提交的批数据中的各 Stream 最大位点,以及更新 Stream 对应的 StreamObjects

  • Node 元数据:更新对应节点的 StreamSetObjects

若在上传过程中,节点发生异常导致上传终止,Controller 则会通过定时任务将超出过期时间依旧未提交的 Object 删除,以避免对象泄漏。

分区数据读取

分区数据的读取同样会转换为对 Stream 的读取,当需要读取的数据段已不再存在于节点缓存中时,就需要向对象存储发起读取,而通过上文介绍的分区写入流程,我们已经知道元数据中已经保存了 Stream 各数据段所在的 Object,此时只需从元数据中索引出需要读取的数据段对应的 Object 列表,再向对象存储发起读取请求即可。这里需要注意的是,由于 AutoMQ 的元数据全部基于 KRaft 机制构建,故上述的元数据变更全部会跟随 KRaft Record 的同步而分发到每台节点上,也即每台 Broker 都缓存有最新的元数据信息,所以索引的过程全部发生在本地缓存中。具体的索引流程如下:

  1. 首先从本地的 Stream 元数据缓存中获取 Stream 对应的 StreamObjects,由于每一个 StreamObject 都对应着 Stream 的一个连续数据段,此时只需从第一个 StreamObject 开始遍历,并将满足范围的加入到结果中,直到遇到第一个超出期望读取的数据范围的 StreamObject,或所有 StreamObject 都已被遍历完毕。

  2. 当对 StreamObject 的遍历退出后,若此时还未满足期望的读取范围,则意味着接下来的数据存在于 StreamSetObject 中,从上述元数据介绍中我们知道,Stream 元数据中记录了 Ranges 信息,其对应着 Stream 在不同节点上的位点范围。而 Ranges 是一个按位点顺序排列的有序列表,此时只需通过一次二分查找即可找到当前期望数据段所在的 Range,和对应 Range 所在的 Node Id。

  3. 找到 Node Id 后即可在 Node 元数据中获取该 Node 对应的 StreamSetObejcts,而每个 StreamSetObject 中都保存了组成该 Object 的各 Stream 的位点信息,此时对 StreamSetObjects 进行一次遍历,即可获得符合当前期望数据段的 StreamSetObject。

  4. 当 StreamSetObjects 遍历完成后,可能依旧存在期望读取的数据还未得到满足的情况,此时会再次进入步骤 1,从上次一的 StreamObject 遍历点开始继续下一轮搜索,直到请求范围得到满足;或因为元数据同步延迟等原因,所有 Object 均遍历完毕依旧没能满足请求,此时会直接将部分满足的请求返回,等待下次请求重试。

可以看到,由于 StreamSetObject 构成的复杂性,索引的大多数成本花费在了对 StreamSetObject 的搜索中,为提升索引速度,AutoMQ 还额外实现了 Compaction 机制,能够使得 Stream 的大多数数据都存在于 StreamObject 中(感兴趣的读者可参考:AutoMQ 对象存储数据高效组织的秘密: Compaction [4])。

04

总结

本文介绍了 AutoMQ 基于 KRaft 的元数据管理机制,相比传统基于 Zookeeper 的元数据管理,Controller 由于成为了所有元数据的处理节点,其稳定性对系统的正常运行起到了至关重要的作用,而 AutoMQ 在进一步拓展了对象存储相关的元数据后,对 Controller 节点的稳定性也提出了更高的要求。为此,AutoMQ 团队也在持续优化元数据规模和索引效率,保障在单一超大规模集群下的高效稳定运行。

参考资料

1\] AutoMQ Release 1.1.0: \[2\] AutoMQ 如何做到 Apache Kafka 100% 协议兼容: \[3\] S3Stream: A Shared Streaming Storage Library: \[4\] AutoMQ 对象存储数据高效组织的秘密: Compaction: **关于我们** 我们是来自 Apache RocketMQ 和 Linux LVS 项目的核心团队,曾经见证并应对过消息队列基础设施在大型互联网公司和云计算公司的挑战。现在我们基于对象存储优先、存算分离、多云原生等技术理念,重新设计并实现了 Apache Kafka 和 Apache RocketMQ,带来高达 10 倍的成本优势和百倍的弹性效率提升。 🌟 GitHub 地址: 💻 官网:

相关推荐
晚霞的不甘1 小时前
Flutter for OpenHarmony天气卡片应用:用枚举与动画打造沉浸式多城市天气浏览体验
前端·flutter·云原生·前端框架
Tadas-Gao1 小时前
TCP粘包现象的深度解析:从协议本质到工程实践
网络·网络协议·云原生·架构·tcp
星辰_mya2 小时前
Es之脑裂
大数据·elasticsearch·搜索引擎
搞科研的小刘选手2 小时前
【EI稳定检索会议】第七届计算机信息和大数据应用国际学术会议(CIBDA 2026)
大数据·acm·学术会议·计算机工程·计算机信息·大数据应用·信息与技术
爆米花byh2 小时前
在RockyLinux9环境的Kafka4.1.1单机版安装(无ZK依赖)
中间件·kafka
成长之路5142 小时前
【数据集】地级市公共安全基建省内横向压力(2015-2025)
大数据
切糕师学AI2 小时前
Helm Chart 是什么?
云原生·kubernetes·helm chart
YangYang9YangYan3 小时前
2026中专大数据专业学习指南
大数据
yumgpkpm3 小时前
预测:2026年大数据软件+AI大模型的发展趋势
大数据·人工智能·算法·zookeeper·kafka·开源·cloudera
无级程序员3 小时前
大数据Hive之拉链表增量取数合并设计(主表加历史表合并成拉链表)
大数据·hive·hadoop