目前,Apache Kafka®使用Apache zooKeeper™来存储其元数据。分区位置和主题配置等数据存储在Kafka本身之外,存储在单独的ZooKeeper集群中。在2019年,我们概述了一个计划,打破这种依赖,将元数据管理引入卡夫卡本身。
那么动物园管理员有什么问题呢?实际上,问题不在于动物园管理员本身,而在于外部元数据管理的概念。
有两个系统会导致大量的重复。毕竟,Kafka是一个复制的分布式日志,上面有一个发布/订阅API。ZooKeeper是一个复制的分布式日志,上面有一个文件系统API。每种设备都有自己的网络通信、安全、监控和配置方式。对于操作员来说,拥有两个系统大致会使结果的总复杂性增加一倍。这会导致不必要的陡峭学习曲线,并增加某些错误配置导致安全漏洞的风险。
在外部存储元数据效率不高。我们至少运行三个额外的Java进程,有时甚至更多。事实上,我们经常看到Kafka集群的ZooKeeper节点和Kafka节点一样多!另外,ZooKeeper中的数据还需要反映在Kafka控制器上,这会导致双重缓存。
更糟糕的是,外部存储元数据限制了Kafka的可伸缩性。当Kafka集群正在启动,或者正在选择新的控制器时,控制器必须从ZooKeeper加载集群的完整状态。随着元数据数量的增加,此加载过程的长度也会增加。这限制了Kafka可以存储的分区数量。
最后,在外部存储元数据可能会使控制器的内存内状态与外部状态不同步。控制器的活度视图(在集群中)也可能与动物园管理员的视图不同。
KIP-500概述了在卡夫卡中处理元数据的更好方法。您可以将其视为“Kafka on Kafka”,因为它涉及到将Kafka的元数据存储在Kafka本身中,而不是存储在ZooKeeper等外部系统中。
在后KIP-500时代,元数据将存储在Kafka内部的分区中,而不是ZooKeeper中。控制器将是此分区的领导者。将不会有外部元数据系统需要配置和管理,只有卡夫卡本身。
我们将元数据视为日志。需要最新更新的代理只能读取日志的尾部。这类似于需要最新日志条目的使用者只需要读取日志的最末尾,而不是整个日志。代理还将能够在进程重新启动时持久保存它们的元数据缓存。
Kafka集群选举控制器节点来管理分区领导者和群集元数据。我们拥有的分区和元数据越多,控制器可伸缩性就变得越重要。我们希望最大限度地减少需要与主题或分区的数量成线性比例的时间的操作数量。
一种这样的操作是控制器故障转移。目前,当Kafka选举新的控制器时,它需要加载完整的集群状态才能继续。随着群集元数据数量的增长,此过程需要的时间越来越长。
相比之下,在KIP-500之后的世界中,将有几个备用控制器,它们随时准备在活动控制器离开时接手。这些备用控制器只是元数据分区的RAFT Quorum中的其他节点。这种设计确保了在选举新的控制器时,我们不需要经历漫长的加载过程。
KIP-500将加快主题的创建和删除。目前,在创建或删除主题时,控制器必须从ZooKeeper重新加载集群中所有主题名称的完整列表。这是必要的,因为当集群中的主题集发生更改时,ZooKeeper会通知我们,但它并不确切地告诉我们添加或删除了哪些主题。相反,在后KIP-500世界中创建或删除主题只涉及在元数据分区中创建一个新条目,这是一个O(1)操作。
元数据可伸缩性是未来扩展Kafka的关键。我们预计单个Kafka集群最终能够支持100万个或更多分区。
作为Kafka版本的一部分提供的几个管理工具仍然允许与动物园管理员直接通信。更糟糕的是,仍然有一两个操作不能完成,除非通过这种直接的动物园管理员通信。
我们一直在努力缩小这些差距。很快,对于以前需要动物园管理员直接访问的每个操作,都将有一个公共的Kafka API。在Kafka的下一个主要版本中,我们还将禁用或删除不必要的--zooKeeper标志。
在后KIP-500时代,Kafka控制器将其元数据存储在Kafka分区中,而不是ZooKeeper中。但是,因为控制器依赖于此分区,所以分区本身不能依赖于控制器来进行领导者选举之类的事情。相反,管理此分区的节点必须实现自我管理的RAFT仲裁。
KIP-595:元数据仲裁的RAFT协议概述了我们将如何使RAFT协议适应Kafka,从而使其真正感觉像是系统的本机部分。这将涉及将RAFT论文中描述的基于推送的模式更改为基于Pull的模式,这与传统的卡夫卡复制是一致的。其他节点将连接到这些节点,而不是将数据推送到其他节点。同样,我们将使用与卡夫卡一致的术语,而不是原始的筏纸--“纪元”而不是“术语”,以此类推。
最初的实施将侧重于支持元数据分区。它不支持将常规分区转换为RAFT所需的全部操作。然而,这是我们未来可能会回到的一个话题。
当然,这个项目最令人兴奋的部分是能够在没有动物园管理员的情况下在“KIP-500模式”下运行。当Kafka在这种模式下运行时,我们将使用RAFT Quorum来存储我们的元数据,而不是ZooKeeper。
最初,KIP-500模式将是试验性的。大多数用户将继续使用“传统模式”,在这种模式下,ZooKeeper仍在使用。这在一定程度上是因为KIP-500模式一开始并不支持所有可能的功能。另一个原因是,我们希望在将KIP-500模式设为默认模式之前获得信心。最后,我们需要时间来完善从传统模式到KIP-500模式的升级过程。
启用KIP-500模式的大部分工作将在控制器中完成。我们必须将与ZooKeeper交互的控制器部分与实现更多通用逻辑(如副本集管理)的部分分开。
我们需要定义和实现更多的控制器API来取代目前涉及ZooKeeper的通信机制。这方面的一个例子是新的AlterIsr API。此API允许副本在不使用ZooKeeper的情况下通知控制器同步副本集中的更改。
KIP-500引入了桥式版本的概念,它可以与KIP-500之前和之后的卡夫卡版本共存。桥接版本很重要,因为它们可以实现对后动物园饲养员世界的零停机升级。使用旧版本卡夫卡的用户只需升级到桥式版本即可。然后,他们可以对缺少ZooKeeper的版本执行第二次升级。顾名思义,桥梁释放就像一座通往新世界的桥梁。
那么这是怎么运作的呢?假设一个集群处于部分升级状态,桥接版本上有几个代理,KIP-500发布后有几个代理。管制员将始终是KIP-500之后的经纪人。在这个集群中,代理不能依赖直接修改ZooKeeper来宣布他们正在进行的更改(例如配置更改或ACL更改)。KIP-500之后的经纪人不会收到这样的通知,因为他们没有监听动物园饲养员。只有控制器仍在与ZooKeeper交互,将其更改镜像到ZooKeeper。
因此,在桥发布中,除控制器之外的所有代理都必须将ZooKeeper视为只读(除了一些非常有限的例外)。
对于像IncrementalAlterConfigs这样的RPC,我们只需要确保调用由活动控制器处理。对于新客户来说,这很容易-他们只需将呼叫直接发送到那里即可。对于较老的客户端,我们需要在将RPC发送到活动控制器的代理上运行的重定向系统,无论它们最初位于哪个代理上。
对于涉及代理和控制器之间复杂交互的RPC,我们将需要创建新的控制器API。一个例子是KIP-497,它指定了一个新的AlterIsrRequest API,允许代理请求更改分区同步副本(ISR)。
将即席ZooKeeper API替换为文档齐全且受支持的RPC与删除客户端ZooKeeper访问具有许多相同的好处。维护跨版本兼容性将变得更容易。对于AlterIsrRequest的特殊情况,减少向ZooKeeper写入普通操作所需的次数也会带来好处。
Kafka是最活跃的Apache项目之一。在过去的几年里,看到它的架构的演变是令人惊叹的。正如KIP-500这样的项目所表明的那样,这种演变还没有完成。我最喜欢KIP-500的地方是它简化了整个架构-无论是管理员还是开发人员。它将允许我们使用事件日志的强大抽象来处理元数据。它将最终证明…。卡夫卡不需要看门人。
如果您对Apache Kafka中动物园管理员的移除有任何疑问,请在5月21日上午10:00加入我们在Confluent Community Slake#普通频道的现场问答。PDT。要了解其他正在进行的使Kafka具有弹性可伸缩性的工作,请查看这篇关于“变形工程”的博客文章。
Colin McCabe目前在Confluent工作,以提高Apache Kafka的性能、可伸缩性和通用性。在此之前,他研究过ApacheHadoop®分布式文件系统(™)。科林在卡内基梅隆大学(Carnegie Mellon University)学习计算机科学和计算机工程,他喜欢自制啤酒和潜水。