Kafka进阶之内部Cluster架构分析

我们在前面的文章中已经介绍了Kafka的基础部分包括Producer,consumer等基本概念,本文来稍微深入聊一聊Kafka内部Cluster的架构。这个部分的理解对我们Debug相关的问题会有很大的帮助。

Cluster成员

Kafka在3. 0版本之前使用的是ZooKeeper来进行管理broker的列表的。每一个broker都用一个唯一的ID来进行标识。当一个broker启动的时候,它会去ZooKeeper中利用它的ID来创建一个ephemeral node。其它的broker包括controller可以subscribe到/brokers/ids这个路径下,这样每次有新的broker加入或者删除的时候都可以得到通知。当然如果有同样的ID试图加入,也会有error信息报出。

另外需要注意的是,当一个broker停止的时候,它的ID其实还是保存在别的结构中的,因此这个时候要是有一个新的同样ID的broker加入,它会立即取代旧的broker,然后可以有同样的partition和topic分配给它。

Controller

Controller说白了就是一个broker,它和别的broker不同的地方在于它还要负责对partition的leader进行选举(一个partition通常有多个replication,所以会是一个leader,多个follower的存在形式)。通常来说第一个加入的member就默认是controller,它会在Zookeeper中创建一个/controller的ephemeral node。这样别的node想要再创建就会发现它的存在,他们会创建一个Zookeeper的watch来监控controller的变化,所以假如旧的controller消失的话会有新的broker来成为controller,从而保证任何时候都会有一个controller的存在。

当新的controller起来之后,它需要从ZooKeeper中读取最新的replica状态,然后才能开始管理cluster的metadata以及进行partition leader的选举。

我们来看下面这个例子这里有三个broker,其中broker 0是controller,broker 1和broker 2各有两个partition,分别是t-0和t-1。开始的时候t-0和t-1的leader都在broker 1上,broker 2上的两个都是follower,如下图所示:

现在我们假设外部有一个请求把broker 1拿shut down了,这个时候broker 1就会发送一个请求到controller说我要down了,broker 0收到这个请求之后会对Broker 1上的partition进行一个leader的处理,这个例子中会选择broker 2为新的leader,在告诉broker2之前它会先把这个新的leader的信息写到Zookeeper中,然后在通知broker 2说你现在是新的leader了,最终再返回response给broker 1说,好了,你现在可以shut down了。

在Kafka 1.1.0之前,这个流程效率是很低的,controller在选举出新的leader之后,会同步写到Zookeeper中,一个一个partition写(步骤3),然后在一个一个partition发送leader的通知(步骤4),也就是说步骤3自己每个partition的写是一个同步的流程,这里有一个延时。同时步骤4会一个一个通知broker 2,当一个broker中有很多partition的时候效率就很低了。

所以在Kafka 1.1.0以及之后的版本中,他们对这个部分进行了优化,第一个优化就是写到Zookeeper的动作改成了一个异步的调用,而且这里不再是一个一个partition写了,是多个partition的异步调用,大大降低了延时。同时在和broker交流的过程中也会把所有的有影响的partition batch起来一起发送。

同样的,在我们上面提到的controller down了再有一个新的Controller的时候,它需要从ZooKeeper中读取状态,1.1.0之前也是一个一个partition同步进行读取的,在1.1.0之后也改成了异步读,从而减少了这个部分的latency。

整个改变,当时在一个5 broker(不同rack)的cluster中,包含50000partition以及2 replica,每个broker 10000 partition的系统中测试的结果如下所示,我们可以看到性能得到了极大的提升。

Kraft

从2019开始,Kafka社区有了一个想法,就是使用基于Raft的controller,而不再使用ZooKeeper。这个功能被称之为Kraft,它在2.8版本中有发布预览版,在3.0的版本中正式发布。整个社区是基于下面这些考虑来决定实现Kraft的:

  1. Metadata在ZooKeeper写是同步的,在broker中的写则是异步的,这很可能会导致broker,controller以及ZooKeeper中的Metadata数据不一致。
  2. 当controller重启的时候,它需要到从ZooKeeper中读取所有的brokers、partition的metadata,然后把这个metadata发送给所有的broker,随着broker和partition的增长,这个延时也越来越严重。
  3. 内部的一些Metadata的数据处理也有问题,有些会通过controller来处理,有些会通过broker,甚至有些就直接去访问ZooKeeper。
  4. ZooKeeper本身就是一个分布式系统,它需要额外的人力来进行学习和maintain。

要想取代ZooKeeper,我们首先要来看看ZooKeeper都做了些什么,然后再看如何用Kraft实现同样的功能。ZooKeeper有两个重要的功能,它被用来选举一个controller,同时还用来保存各种metadata,包括有哪些broker,各种configuration,topics,partitions以及replicas。同时选举出来的controller还需要负责各个partition的leader的选举,创建、删除topics以及重新assign replica等等。这些就是新的Kraft需要实现的主要功能。

我们知道Kafka本身有一套基于log的架构,主要是用来给多个consumer快速catch 用的,它可以保证每个consumer的时间线是一样的。这个思想其实在现在的分布式系统中很普遍,而Kraft也是使用这个思想,每次metadata改变都会产生一个log,有了这个log就可以replay出来所有的metadata的当前状态。

首先各个broker可以直接使用Raft来选举出来一个leader成为controller,也就是说controller的选举不需要再依赖于ZooKeeper了。我们把这个controller称之为active controller,别的broker称之为follower controller,它们replicate所有写到active controller的操作,从而成为一个随时待命的controller,当真正的active controller出问题的时候,他们能快速站出来,成为新的active的controller。这个过程就会很快,因为follower controller其实有所有需要的信息,随时处于ready的状态。

另外Kraft还会周期性地进行snapshot的保存以保证上述的event log不会无限制的增长,所以整个架构大概就如下所示:

有了这样的优化之后,它的测试性能结果如下所示,我们可以看到整个replace工作的价值还是很大的。

总结

本文详细介绍了Kafka内部Cluster的架构,包括之前使用的ZooKeeper和现在实现的Kraft。这种变迁也从某种程度上说明了分布式系统的趋势,希望对大家深入理解Kafka有所帮助。

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *