1. 引言

Kafka 架构最近经历了一次重大变革:从依赖 ZooKeeper 转向基于仲裁的控制器(quorum-based controller),采用名为 Kafka Raft(简称 KRaft,发音同 "craft")的新共识协议。

本文将深入探讨 Kafka 做出这个决策的原因,以及这次变革如何简化架构并提升系统性能。

2. ZooKeeper 快速回顾

ZooKeeper 是一个实现高可靠分布式协调的服务。最初由雅虎开发,用于简化大数据集群上的流程管理。它最初是 Hadoop 的子项目,2008 年成为独立的 Apache 基金会项目。在大型分布式系统中被广泛用于解决多种协调问题。

2.1. ZooKeeper 架构

ZooKeeper 采用分层命名空间存储数据,类似标准文件系统。命名空间由称为 znode 的数据寄存器组成,名称是由斜杠分隔的路径元素序列。

命名空间中的每个节点都通过路径标识: ZooKeeper 数据模型

ZooKeeper 命名空间中有三种类型的 znode:

  • 持久节点:默认类型,会一直存在直到被显式删除
  • 临时节点:当创建该节点的会话断开时自动删除,且不能有子节点
  • 顺序节点:可用于生成序列号(如 ID)

通过简洁的架构,ZooKeeper 提供了可靠、高性能且可扩展的系统。它被设计为在一组称为 ensemble 的服务器上复制运行。每台服务器维护内存中的状态镜像,并在持久存储中保存事务日志和快照: ZooKeeper 架构

ZooKeeper 客户端只连接一台服务器,但当服务器不可用时可以故障切换到其他服务器。读请求由各服务器数据库的本地副本处理;写请求则通过协议处理,所有写请求会被转发到 leader 服务器,由其使用 ZooKeeper 原子广播(ZAB)协议进行协调。

本质上,原子消息传递是 ZooKeeper 的核心,它保持所有服务器同步,确保消息可靠传递,并保证消息按全局因果顺序传递。消息系统通过 TCP 在服务器间建立点对点 FIFO 通道。

2.2. ZooKeeper 的用途

ZooKeeper 为客户端的所有更新提供顺序一致性和原子性保证,且不允许并发写入。无论客户端连接到哪台服务器,看到的都是一致的服务视图。总体而言,ZooKeeper 在高性能、高可用性和严格有序访问方面提供了出色的保证。

ZooKeeper 还实现了极高的吞吐量和低延迟。这些特性使其成为解决大型分布式系统协调问题的理想选择,包括命名服务、配置管理、数据同步、领导者选举、消息队列和通知系统等场景。

3. ZooKeeper 在 Kafka 中的角色

Kafka 是一个分布式事件存储和流处理平台。最初由 LinkedIn 开发,2011 年在 Apache 软件基金会下开源。Kafka 为处理实时数据流提供高吞吐量、低延迟的平台,广泛用于流分析、数据集成等高性能场景。

3.1. Kafka 架构

Kafka 是一个由服务器和客户端组成的分布式系统,通过基于 TCP 的二进制协议通信。它被设计为以集群(由一个或多个称为 broker 的服务器组成)方式运行。broker 同时也充当事件的存储层。

Kafka 将事件持久化组织在主题(topic)中。主题可以有零个、一个或多个生产者和消费者。主题被分区并分布在不同 broker 上以实现高可扩展性。此外,每个主题都可以在集群中复制: Kafka 架构

在 Kafka 集群中,其中一个 broker 充当控制器。控制器负责管理分区和副本的状态,并执行分区重分配等管理任务。集群中任何时候只能有一个控制器。

客户端使应用程序能够以并行、大规模、容错的方式读取、写入和处理事件流。生产者是向 Kafka 发布事件的客户端应用,消费者则是从 Kafka 订阅这些事件的应用。

3.2. ZooKeeper 的角色

Kafka 的设计特性使其具有高可用性和容错性。但作为分布式系统,Kafka 需要一种机制来协调所有活跃 broker 之间的决策,并维护集群及其配置的一致视图。长期以来,Kafka 一直使用 ZooKeeper 来实现这一点。

直到最近转向 KRaft 之前,ZooKeeper 一直作为 Kafka 的元数据管理工具,承担以下关键功能: ZooKeeper 在 Kafka 中的角色

  • 控制器选举:严重依赖 ZooKeeper。每个 broker 尝试在 ZooKeeper 中创建临时节点,第一个成功创建的 broker 成为控制器并被分配控制器纪元(epoch)
  • 集群成员管理:当 broker 连接到 ZooKeeper 时,会在组 znode 下创建临时 znode。如果 broker 故障,该临时 znode 会被删除
  • 主题配置:Kafka 在 ZooKeeper 中维护每个主题的配置(全局或特定主题),还存储现有主题列表、分区数和副本位置等信息
  • 访问控制列表(ACL):Kafka 在 ZooKeeper 中维护所有主题的 ACL,用于控制读写权限,同时存储消费者组及其成员信息
  • 配额管理:Kafka broker 通过 ZooKeeper 存储配额来控制客户端资源使用,包括基于字节率阈值的网络带宽配额和基于 CPU 利用率阈值的请求速率配额

3.3. ZooKeeper 带来的问题

如前所述,ZooKeeper 在 Kafka 架构中长期扮演着重要角色。那为什么决定改变它?简单来说,ZooKeeper 为 Kafka 增加了一个额外的管理层。管理分布式系统本身就是复杂任务,即使像 ZooKeeper 这样简单健壮的系统也不例外。

Kafka 并非唯一需要协调成员间任务的分布式系统,MongoDB、Cassandra 和 Elasticsearch 等系统都通过各自方式解决这个问题。但它们不依赖 ZooKeeper 这样的外部工具进行元数据管理,而是使用内部机制

除了其他好处,这使部署和运维管理更简单。想象一下,只需管理一个分布式系统而不是两个!此外,更高效的元数据处理也提升了可扩展性。将元数据存储在 Kafka 内部而非 ZooKeeper 中,使管理更简单并提供更好的保证。

4. Kafka Raft (KRaft) 协议

受 Kafka 与 ZooKeeper 组合复杂性的启发,社区提交了一个 Kafka 改进提案(KIP),用自管理的元数据仲裁替代 ZooKeeper。基础提案 KIP 500 定义了愿景,后续多个 KIP 完善了细节。自管理模式在 Kafka 2.8 中作为早期访问版本首次发布。

自管理模式将元数据管理职责整合到 Kafka 内部。该模式使用 Kafka 中新的仲裁控制器服务,仲裁控制器采用事件溯源存储模型,并使用 Kafka Raft(KRaft)作为共识协议,确保元数据在仲裁中准确复制。

KRaft 本质上是Raft 共识协议的事件驱动变体,与 ZAB 协议类似,但显著区别在于其事件驱动架构。仲裁控制器使用事件日志存储状态,并定期压缩为快照以防止无限增长: Kafka KRaft 协议

仲裁控制器中的一个作为 leader,在 Kafka 内部的元数据主题中创建事件。仲裁中的其他控制器通过响应这些事件跟随 leader。当某个 broker 因分区故障失效时,重新加入后可以从日志中追赶错过的事件,这减少了不可用时间窗口。

与基于 ZooKeeper 的控制器不同,仲裁控制器不需要从 ZooKeeper 加载状态。当领导者变更时,新的活跃控制器已在内存中拥有所有已提交的元数据记录。此外,同样的事件驱动机制也用于跟踪集群中的所有元数据。

5. 更简单且更强大的 Kafka!

转向基于仲裁的控制器为 Kafka 社区带来了显著改进: ✅ 运维更简单:系统管理员会发现监控、管理和支持 Kafka 更容易 ✅ 安全模型统一:开发者只需处理整个系统的单一安全模型 ✅ 轻量级部署:提供单进程部署方式快速启动 Kafka

新的元数据管理显著提升了 Kafka 的控制平面性能: ✅ 控制器故障切换更快:不再依赖 ZooKeeper 的会话超时机制 ✅ 支持更多分区:基于 ZooKeeper 的元数据管理曾是集群分区数量的瓶颈,新仲裁控制器设计为支持每个集群多得多的分区

自 2.8 版本起,Kafka 同时支持 ZooKeeper 和 KRaft 模式。3.0 版本中作为预览功能发布,经过多次改进后,在 3.3.1 版本中宣布生产就绪。Kafka 可能在 3.4 版本中弃用 ZooKeeper。但可以肯定的是,使用 Kafka 的体验已显著提升

6. 结论

本文详细介绍了 ZooKeeper 及其在 Kafka 中的作用,分析了该架构的复杂性,并阐述了 Kafka 选择用基于仲裁的控制器替代 ZooKeeper 的原因。

最后,我们探讨了这次变革在简化架构和提升可扩展性方面为 Kafka 带来的好处。这次演进不仅解决了运维痛点,还为 Kafka 的未来发展奠定了更坚实的基础。


原始标题:Kafka’s Shift from ZooKeeper to Kraft | Baeldung