Zookeeper和KRaft这里是Kafka功能改进的原提案。要了解移除ZK的原因,您可以仔细阅读这篇文章。以下为文章译文。动机目前,Kafka使用Zookeeper来存储与分区、代理相关的元数据,以及选举Kafka控制器(代理)。我们将移除对Zookeeper的依赖。通过这种方式,Kafka将在管理元数据方面实现更好的可扩展性和健壮性,同时支持更多的分区。在Kafka的部署配置方面,也会大大简化。将元数据视为事件日志我们常说状态是事件流管理的好处。描述消费者在流中的位置的数字:偏移量。消费者可以通过偏移后的事件回放得到最新的状态。日志建立了清晰有序的事件机制,确保每个消费者都能获得自己的时间线。尽管我们的用户享受着这些便利,却忽略了Kafka本身。我们将影响元数据的更改视为相互独立且无关的。当控制器将状态更改通知集群中的其他代理时,其他代理可能会收到部分更改,但不会收到所有更改。虽然控制器会重试几次,但最终还是会停止重试。这将导致代理不同步。更糟糕的是,尽管Zookeeper存储记录,但Zookeeper中保存的状态通常与控制器保存在内存中的状态不匹配。例如,当分区领导者在Zookeeper中更改其ISR(同步副本)时,通常,控制器会延迟几秒钟以了解其更改。对于控制器来说,没有通用的方法来跟踪Zookeeper的事件日志。虽然控制器可以设置一次性守卫,但由于性能问题,守卫的数量是有限的。触发守卫时,守卫不负责通知控制器当前状态,而只负责通知控制器状态的变化。同时,controller重读znode然后设置新的守卫,但是在通知初始守卫到controller完成重读并重置守卫之间,状态可能发生了新的变化。没有警卫,控制器永远不会被告知变化。在某些情况下,重新启动控制器是解决状态不一致的唯一方法。元数据存储在Kafka中比存储在单独的系统中更好。在这种情况下,控制器状态和Zookeeper状态之间的差异相关问题将不再存在。让代理从事件日志中消费元数据事件,而不是一个一个地通知代理。这确保元数据更改以相同的顺序同步到代理。代理将元数据存储在本地文件中。当这些代理启动时,他们只需要从控制器(代理)读取更改,而不是完整的状态。在这种情况下,我们可以获得更多的分区,同时消耗更少的CPU资源。简化部署和配置Zookeeper是一个独立的系统,有其配置文件语法、管理工具和部署模式。这意味着系统管理员需要学习如何管理和部署两个独立的分布式系统,才能部署Kafka。对于系统管理员来说,这可能是一项艰巨的任务,尤其是当他们不熟悉部署Java服务时。一个统一的系统将极大地改善首次运行Kafka的体验,并有助于扩大其影响范围。因为Kafka和Zookeeper的配置文件是分开的,非常容易出错。例如,管理员在Kafka中设置了SASL(SimpleAuthenticationSecurityLayer,简单认证安全层),误认为网络中传输的所有数据都是加密的。其实加密也需要在外部系统Zookeeper中配置。统一两个系统将产生一个完整的加密配置模型。最后,未来我们可能需要支持单节点的Kafka模型。对于想测试Kafka功能的人来说,不需要启动守护进程,会提供很大的方便。删除Zookeeper依赖项将使这成为可能。ArchitectureIntroduction这个KIP(KafkaImprovementProposal,卡夫卡改进提案)展示了后Zookeeper时代可扩展的Kafka系统的整体愿景。为了突出重要部分,我省略了大部分细节,如RPC格式、磁盘格式等。在后续的KIP中,我们将逐步深入描述细节。与KIP-4类似,提出了一个总体愿景,将在后续的KIP中逐步扩展。概述目前,一个Kafka集群包括若干broker节点,Zookeeper节点作为外部quorum(投票机制,少数服从多数)。我们画了4个broker节点和3个Zookeeper节点。这是小型集群所需的正常配置。Controllers(indicatedinorange)loadtheirstatefromtheZoopeeperquorumafterbeingelected.controller到其他节点的线路代表了broker中updatecontroller推送的消息,比如LeaderAndIsr和UpdateMetadata消息。请注意,这张图片具有误导性。除了controller,其他broker也可以和Zookeeper通信。因此,每个经纪人都应该画一条连接ZK的线。但是,绘制太多线条会使图形难以阅读。该图还忽略了无需控制器干预即可修改Zookeeper中的状态的外部命令行工具和工具包。如上所述,这些问题导致控制器的内存状态不能真正反映Zookeeper中的持久状态。在提议的架构中,三个控制器节点取代了Zookeeper的三个节点。控制器节点和代理节点运行在不同的JVM中。控制器节点为元数据分区选择领导节点,以橙色标记。与控制器向每个代理推送元数据更新相比,在Proposed中,每个代理从领导者那里拉取元数据更新。这就是箭头指向控制器的原因。请注意,控制器进程与代理进程在逻辑上是隔离的,它们不必在物理上隔离。在某些情况下,将部分或全部控制器进程和代理进程部署在同一节点上是有意义的。这类似于Zookeeper进程和Kafkabroker部署在同一个节点上(目前小集群的部署方式)。通常,可以采用多种部署方法,包括在同一个JVM中运行。控制器仲裁控制器节点由管理元数据日志的Raft仲裁(Raft选举机制)组成。该日志包含有关集群元数据的每次更改的信息。目前所有的信息都存储在Zookeeper中,比如topic、partition、ISR、configuration等,在新的架构中,这些信息都会存储在log中。通过Raft算法,控制器节点将在不依赖任何外部系统的情况下在它们之间选举领导者。元数据日志的领导者称为活动控制器。活动控制器处理来自代理的所有RPC调用。跟随控制器(与领导控制器相反)从活动控制器复制所有写入,并在活动控制器发生故障时充当热备用。由于控制器完全跟踪最新状态,控制器故障转移将不需要花费大量时间将最新状态传输到新控制器。与Zookeeper一样,Raft需要大多数节点都处于运行状态才能正常运行。因此,一个3节点控制器集群可以容忍一个节点故障。5个节点的控制器集群允许两个节点发生故障,依此类推。控制器会定期将元数据快照写入磁盘。虽然在概念上类似于压缩,但代码路径略有不同,因为我们从内存中读取状态而不是从磁盘重新读取日志。管理代理元数据不同于控制器将更新推送到单个代理,后者将通过新的MetadataFetchAPI从活动控制器中拉取更新。MetadataFetch类似于拉取请求。就像拉取请求一样,代理将跟踪它拉取的最新更新的偏移量,并且只从活动控制器请求新的更新。代理将获取的元数据保存到磁盘。这样broker的启动速度会非常快,即使有几百上千个分区,甚至上百万个分区。(注意这个持久化是一种优化,如果忽略这个优化可以提高开发效率,那么我们在第一个版本中可以忽略它。)大多数时候,broker只需要拉取增量状态(deltas),而不是满状态。但是,如果代理的状态与活动控制器的状态相差太远,或者代理根本没有缓存的元数据,则控制器将返回完整的元数据镜像,而不是返回增量数据列表。代理定期从活动控制器请求元数据更新。请求同时作为心跳发送,让控制器知道代理是活的。请注意,虽然本节仅讨论管理代理元数据,但管理客户端元数据对于可伸缩性也很重要。一旦用于发送增量元数据更新的基础设施到位,该基础设施将被客户和经纪人使用。毕竟一般情况下,客户的数量会大于经纪人的数量。随着分区数量的增加,客户端将对更多分区感兴趣,因此以增量方式向客户端交付元数据更新将变得越来越重要。我们将在接下来的几小节中讨论这个问题。Broker状态机目前,broker启动后会立即在Zookeeper中注册自己。注册过程完成两件事:它告诉代理它是否被选为控制器,它让其他节点知道如何联系它。在后Zookeeper世界中,broker将自己注册到controllerquorum,而不是Zookeeper。目前,可以联系Zookeeper但被控制器分区的代理可以继续服务用户请求,但不会接收任何元数据更新。这可能会导致一些令人困惑、难以驾驭的情况。比如一个producer通过acks=1继续向leader发送数据,但实际上leader已经不是真正的leader了,只是失败的leader无法接收到controller的LeaderAndIsrRequest,从而移除leader状态。在后ZK世界中,集群成员身份集成在元数据更新中。如果代理无法接收元数据更新,它将从集群成员中删除。虽然代理可能仍被特定客户端分区,但如果代理被控制器分区,它仍会从集群中删除。Broker状态Offline当broker进程处于Offline状态时,要么没有启动,要么正在执行启动所需的单节点任务,例如初始化JVM或执行恢复日志。Fenced当broker处于Fenced状态时,将不再响应客户端的RPC请求。broker启动后,尝试拉取最新的元数据时会处于fenced状态。如果无法联系到活动控制器,代理将重新进入fenced状态。发送给客户的元数据应该忽略受保护的代理。在线当代理状态为在线时,表示代理已准备好响应客户请求。Stoppingbroker进入stoppoing状态以表明它们收到了SIGINT信号。这个信号表明系统管理员想要关闭代理。当代理处于停止状态时,它仍在运行,但我们尝试从代理中删除分区领导者。最后,活动控制器向MetadataFetchResponse添加一个特殊的代码字符串,要求代理下线。或者,如果领导者在预定义的时间内处于非活动状态,则代理将关闭。很多在将现有API迁移到控制器之前直接写到Zookeeper的操作都会写到控制器。例如,更改配置、修改持有默认授权的ACL等。较新版本的客户端应将这些操作直接发送到活动控制器。这是一个向后兼容的更改:它在新旧集群中都可以正常工作。为了与旧客户端兼容,这些操作将随机发送到代理,代理将这些请求转发到活动控制器。NewControllerAPI在某些情况下,我们需要创建一个新的API来替换之前通过Zookeeper完成的操作。比如partitionleader要修改in-syncreplicas的集合,在后ZK世界,直接修改Zookeeper,现在leader向activecontroller发起RPC请求。从工具包中删除了对Zookeeper的直接访问目前,一些工具和脚本直接与Zookeeper联系。在后Zookeeper世界中,这些工具将被KafkaAPI取代。幸运的是,几年前开始取消对Zookeeper的直接访问的“KIP-4:命令行和集中管理操作”几乎完成了。
