Kafka Linking:揭秘 AutoMQ 如何实现全球首个 Zero-Downtime Kafka 迁移方案

01
前言
AutoMQ 是一款基于云存储全新设计的 Kafka 发行版,相较传统的 Apache Kafka 拥有 10 倍的成本优势和秒级的弹性能力。为帮助用户从原有的 Kafka 集群无损切换至 AutoMQ,我们在 5.0 版本中发布了 Kafka Linking —— 一种无停机时间(Zero Downtime)的全托管跨集群数据同步功能。当前业界现有的多种 Kafka 集群数据同步工具,如 MirrorMaker 2 [1], Confluent Cluster Linking [2], WarpStream Orbit [3] 等,在实现集群迁移时均要求发送端和消费端进行“停机”、“等待”、“重启”的三步过程,该操作过程不仅冗长,且由于等待时间不可控,导致迁移过程风险难以评估,从而进一步降低用户的使用体验。而使用 AutoMQ Kafka Linking 进行集群迁移,客户端仅需完成一次滚动升级,将客户端从原有的 Kafka 集群指向 AutoMQ 集群即可完成迁移,相较现有方案极大减少了人为操作成本。本文将介绍如何使用 AutoMQ Kafka Linking 从 Kafka 集群无损迁移至 AutoMQ,以及 Kafka Linking 背后的技术原理。
02
现有技术方案的痛点
以 Confluent 为例,下图是使用 Confluent Cluster Linking 进行集群迁移的过程 [4]:

ref:https://docs.confluent.io/cloud/current/_images/cluster-link-migrate-cc.png
可以看到,在迁移过程中,需要先停止源集群生产者,等待集群同步延迟为零后,对 Mirror Topic 进行 Promote(即停止同步),然后再将生产者重启并指向目标集群,在此过程可能导致分钟级的不可用时间。而业界其余方案(MM2, Orbit 等)的迁移过程也基本类似,原因在于这类方案均采用了单向同步链路,为避免消息分叉,在进行客户端迁移时需保证源集群与目标集群的数据已完全对齐且无新增。而 AutoMQ 在单向同步的基础上额外实现了反向转发,支持源集群和目标集群同时写入并保证写入一致性,从而实现一次滚动升级即可完成迁移。
03
使用 Kafka Linking 进行集群迁移
本节将介绍如何从任意一个支持 Kafka 协议的集群迁移至 AutoMQ 集群,下图为迁移步骤概览:使用 AutoMQ Console UI 进行迁移的具体步骤可参考 AutoMQ 官方文档:https://www.automq.com/docs/automq-cloud/migrate-to-automq/executing-migration

创建 Kafka Link
Kafka Link 代表了一个源集群到 AutoMQ 集群的同步链接,是管理一组待迁移的 Topic 和 Consumer Group 的最小单元,创建 Kafka Link 仅包含将源集群信息持久化至 AutoMQ,此时并不会产生其他额外资源创建和请求操作。

完成 Kafka Link 的创建后,AutoMQ 中已经记录下了 Kafka Link 对应的源集群的配置信息,后续将使用该配置信息与源集群建立连接。
创建 Mirror Topic
对所有待迁移的源集群 Topic 在 AutoMQ 集群创建相应的 Mirror Topic 以开始数据同步。Mirror Topic 在 AutoMQ 中拥有和普通 Topic 相同的读写能力,在这之上额外拥有对源集群 Topic 的同步和转发能力。如下图所示,在 AutoMQ 集群中创建了与源集群同名的 2 分区 Topic,Topic 创建完成时,数据同步即随之开始,此时从 Producer 发出的消息等效于双写源集群和目标集群。

创建 Mirror Consumer Group
对所有待迁移的 Consumer Group 在 AutoMQ 集群创建相应的 Mirror Consumer Group,该步骤是为后续同步消费位点做的前置准备,此时并不进行任何位点同步
迁移 Producer
完成上述资源创建后,即可直接进行 Producer 的迁移,由于 AutoMQ Kafka Linking 具备消息转发能力,用户可直接通过一轮滚动更新将 Producer 指向 AutoMQ 集群,此时所有发送至 AutoMQ 集群的消息会被直接转发回源集群,在保证发送不断流的情况下,源集群的 Consumer 还可持续消费到最新的消息。

迁移 Consumer
和迁移 Producer 类似,用户仅需进行一次滚动更新将 Consumer 指向 AutoMQ 集群即可,需要注意的是,为避免迁移过程中同一 Consumer Group 同时消费源集群和 AutoMQ 集群导致消费位点相互覆盖从而导致重复消费,AutoMQ 在此过程中会对连接到 AutoMQ 集群的 Consumer 禁读,当 Consumer 迁移完成全部指向 AutoMQ 时,需对 Consumer Group 进行 Promote。Promote Consumer Group 意味着用户已确认源集群所有 Consumer 都已下线。
注:使用 AutoMQ Console UI 进行迁移时,AutoMQ 可自动进行源集群 Consumer 感知及 Group Promote,无需用户手动操作),此时 AutoMQ 集群会从源集群同步该 Consumer Group 的消费位点并放开读取,此时指向 AutoMQ 集群的 Consumer 可接续源集群的消费位点继续消费。

Promote Topic
当 Producer 和 Consumer 都已完成迁移,用户可手动 Promote Topic 断开源集群和 AutoMQ 集群的链接,Promote 完成后,所有新消息均不会再转发回源集群,AutoMQ 集群也不会再从源集群同步消息。

至此 "topic-a" 从源集群到 AutoMQ 集群的迁移完成,对于源集群的其余 Topic 可重复进行上述步骤完成分批迁移。
04
Kafka Linking 技术原理
本节将从一个 Mirror Topic 从创建到 Promote 的完整生命周期来介绍 Kafka Linking 背后的技术原理。
Mirror Topic 感知及预处理
与负责副本数据同步的 Apache Kafka ReplicaFetcherManager 类似,Kafka Linking 会监听 Metadata Image 变更,当感知到 Partition Leader 变更时,会将 Partition 加入预处理队列,Kafka Linking Manager 会异步从队列中取出 Partition 进行如下预处理:1.通过 Partition 元数据过滤出需要被同步的分区。2.2.获取 Partition 归属的 Kafka Link ID,并索引到 Kafka Link 对应的源集群配置信息。3.3.根据源集群配置建立与源集群的连接,获取源集群元数据。4.4.通过源集群元数据获取 Partition 在源集群的 Leader 和副本分布。5.5.按照 Rack-aware 优先原则选择数据同步连接的目标节点,选择 Leader 作为数据转发的目标节点。6.根据 Partition 和选择的目标节点以及并行度限制,将分区路由到不同的 Fetcher 和 Router。

以上图为例,有三个分区(topic-a:0, topic-a:1, topic-b: 0)被创建至 AutoMQ 集群的 Node-0,而这三个分区在源集群中分布在三个不同节点,根据上述的预处理流程,分区会优先选择同 Rack 的节点作为数据同步节点,选择 Leader 所在节点作为数据转发节点,图中的 Fetcher 负责从数据同步节点拉取数据并写入本地,Router 负责将收到的消息发送至数据转发节点。如图所示,三个分区都选择了与本节点同 Rack 的 Node-0 作为同步节点,同时根据并发度配置(假设同步并发为 2),路由至两个 Fetcher 中。而由于转发节点均不相同,故路由到了三个不同的 Router 中。
数据同步
在上一小节中提到,每个分区最终会被路由到唯一一个 Fetcher 中,Fetcher 的数据同步流程如下:**1.确定起始拉取位点:**当 Fetcher 感知到新分区的加入时,会根据分区元数据决定分区的初始拉取位点。a.若分区为首次创建,则根据分区的起始位点时间配置(最新、最早或根据时间戳决定)向源集群获取分区位点。b.若分区不为首次创建,则使用分区最新位点作为起始拉取位点。**2.分区位点修正:**若分区为首次创建,则根据起始拉取位点对分区进行 truncate。**3.请求构造:**与 Apache Kafka Consumer 实现类似,Fetcher 也通过 FetchSessionHandler 构建增量拉取请求,减小冗余网络流量。**4.响应处理:**Fetcher 获取到请求响应后,会根据响应错误类型进行分别处理。a.存在分区错误:根据具体错误类型,对分区进行元数据更新、移除、重新路由 Fetcher、或退避重试的处理。b.不存在分区错误:对响应数据进行必要元信息修正后 Append 到本地存储。**5.状态更新:**当一次 Fetch 请求的响应处理完成后,Fetcher 会根据 Append 结果更新各分区下一次拉取位点,并重复 3~5 步骤进行持续数据同步。
数据转发
与 Fetcher 相对应,每个分区同样会被路由到唯一一个 Router 中,该分区收到的所有发送请求都会被路由至 Router 进行重新攒批和转发,消息转发的主要流程如下:**1.消息重新映射:**所有被路由到 Router 的消息均会被重新映射到内存中的待发送队列中,Key 为 Topic-Partition,Value 为该分区下所有待发送的消息组成的待发送消息池,在消息池中会根据消息来源的 Producer 进行再次分组,确保来自同一 Producer 的消息遵循 FIFO 的顺序被发出。**2.请求聚合:**由于 Router 收到的消息已经在 Producer 完成了 Record -> Batch 的聚合,Router 不会再次对同一分区的 Batch 进行二次聚合,而是在构建每个发送请求时,仅从每个分区的待发送消息池中挑选一个 Batch 加入发送请求中。**3.请求发送:**Router 完成一次发送请求构建后会从连接池中挑选合适的 Client 进行异步发送,并立即开始构建下一个发送请求,实现并发发送的效果。**4.响应处理:**当一次发送请求成功响应后,Router 会将该请求包含的分区对应的 Producer 的发送队列重新放回待发送消息池,使得该 Producer 的其余消息可在下一次请求中被发出。以下图为例,Producer-0 和 Producer-1 两个发送者分别向 topic-a:0 和 topic-b:1 两个分区发送消息 batch-0 ~ batch-2,以及 batch-0 ~ batch-1,经过 Router 的重新聚合后,这 5 条消息被聚合为 3 个发送请求,来源于 Producer-0 的消息被严格串行发送,保证同 Producer 消息有序,来源于不同 Producer 的消息并行发送,提高转发吞吐。

Topic Promote
Topic Promote 是集群迁移的最后一步,在触发 Topic Promote 后,AutoMQ 会伺机断开与源集群的同步和转发链路,其主要流程如下:1.在 Fetcher 构建拉取请求时,会遍历所有待拉取分区,发现被标记为 Promote 状态的分区时,标记本次 Fetch 请求为一次 Full-Fetch(即响应中强制返回所有请求的分区结果,即便结果为空)。2.Fetcher 收到响应后,判断 Promote 分区的同步延迟是否足够小:a.延迟不足够小:表示此时数据还在追赶中,继续后续正常同步请求。b.延迟足够小:触发分区所在 Router 的 Fence 操作,停止 Router 转发该分区后续 Produce 请求(避免源集群分区持续收到新消息),并等待所有 In-Flight 转发请求全部响应后标记分区状态,此时可确认源集群对应分区已无任何在途新消息。3.在下一轮 Fetch 请求构建时再次标记为 Full-Fetch,判断同步延迟为 0 时,表示该分区所有数据均同步完成,此时标记分区为 Promote 完成,分区从 Fetcher 和 Router 中移出,后续新写入消息直接写入 AutoMQ 集群本地。分区在 Fetcher 中的状态机流转如下:

05
结语
本文介绍了如何使用 AutoMQ Kafka Linking 进行集群迁移,以及 Kafka Linking 背后的技术原理。Kafka Linking 不仅可以用于高效无损的集群迁移,未来 AutoMQ 还会对其进行持续迭代,支持基于 Kafka Linking 的集群容灾、多活、跨集群数据共享等企业级能力。
参考资料
[1]Geo-Replication: https://kafka.apache.org/40/documentation/#georeplication[2]Confluent Cluster Linking: https://docs.confluent.io/cloud/current/multi-cloud/cluster-linking/index.html[3] WarpStream Orbit: https://docs.warpstream.com/warpstream/byoc/orbit[4] Migrate Data with Confluent Cluster Linking: https://docs.confluent.io/cloud/current/multi-cloud/cluster-linking/migrate-cc.html
END
版权声明: 本文为 InfoQ 作者【AutoMQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/6254d46a1ca6ce1caa03e829c】。文章转载请联系作者。
评论