本文所述 AutoMQ 的元数据管理机制均基于 AutoMQ Release 1.1.0 版本 1。
01
前言
AutoMQ 作为新一代基于云原生理念重新设计的 Apache Kafka 发行版,其底层存储从传统的本地磁盘替换成了以对象存储为主的共享存储服务。对象存储为 带来可观成本优势的同时,其与传统本地磁盘的接口和计费方式的差异也为 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 都维护了相应的元数据,主要由以下部分组成:
每当 Controller 接收到 Stream 的相关操作时(如 create, open, commit, trim 等),都会产生相应的 S3StreamRecord,通过 KRaft 层持久化后将状态更新到内存中,并同步更新到各个 Broker 的元数据缓存。
Node 元数据
Node 元数据由以下部分组成:
其中, 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 更新元数据:
若在上传过程中,节点发生异常导致上传终止,Controller 则会通过定时任务将超出过期时间依旧未提交的 Object 删除,以避免对象泄漏。
分区数据读取
分区数据的读取同样会转换为对 Stream 的读取,当需要读取的数据段已不再存在于节点缓存中时,就需要向对象存储发起读取,而通过上文介绍的分区写入流程,我们已经知道元数据中已经保存了 Stream 各数据段所在的 Object,此时只需从元数据中索引出需要读取的数据段对应的 Object 列表,再向对象存储发起读取请求即可。这里需要注意的是,由于 AutoMQ 的元数据全部基于 KRaft 机制构建,故上述的元数据变更全部会跟随 KRaft Record 的同步而分发到每台节点上,也即每台 Broker 都缓存有最新的元数据信息,所以索引的过程全部发生在本地缓存中。具体的索引流程如下:
可以看到,由于 StreamSetObject 构成的复杂性,索引的大多数成本花费在了对 StreamSetObject 的搜索中,为提升索引速度,AutoMQ 还额外实现了 Compaction 机制,能够使得 Stream 的大多数数据都存在于 StreamObject 中(感兴趣的读者可参考:AutoMQ 对象存储数据高效组织的秘密: Compaction 4)。
04
总结
本文介绍了 AutoMQ 基于 KRaft 的元数据管理机制,相比传统基于 Zookeeper 的元数据管理,Controller 由于成为了所有元数据的处理节点,其稳定性对系统的正常运行起到了至关重要的作用,而 AutoMQ 在进一步拓展了对象存储相关的元数据后,对 Controller 节点的稳定性也提出了更高的要求。为此,AutoMQ 团队也在持续优化元数据规模和索引效率,保障在单一超大规模集群下的高效稳定运行。
参考资料
1 AutoMQ Release 1.1.0:https://github.com/AutoMQ/automq/releases/tag/1.1.0
2 AutoMQ 如何做到 Apache Kafka 100% 协议兼容:https://mp.weixin.qq.com/s/ZOTu5fA0FcAJlCrCJFSoaw
3 S3Stream: A Shared Streaming Storage Library:https://github.com/AutoMQ/automq/tree/main/s3stream
4 AutoMQ 对象存储数据高效组织的秘密: Compaction:https://mp.weixin.qq.com/s/z_JKxWQ8YCMs-fbC42C0Lg
关于我们
我们是来自 Apache RocketMQ 和 Linux LVS 项目的核心团队,曾经见证并应对过消息队列基础设施在大型互联网公司和云计算公司的挑战。现在我们基于对象存储优先、存算分离、多云原生等技术理念,重新设计并实现了 Apache Kafka 和 Apache RocketMQ,带来高达 10 倍的成本优势和百倍的弹性效率提升。
🌟 GitHub 地址:https://github.com/AutoMQ/automq
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。