对应视频讲解:
www.bilibili.com/video/BV1ku...
我们讨论一个重要话题,就是 Kafka 官方为什么要放弃Zookeeper?这样大家能够更好的去理解为什么 Kakfa 官方要研发 Kraft 的这样的一个架构来取代 Zookeeper,我们学习知识要做到知其然,也要知其所以然。同时,这也是面试官经常问的问题。
Zookeeper 使用中的问题
既然 Kafka 官方要最终替换 Zookeeper 的,那么肯定生产环境中过程中出现了一些问题,它主要有哪些问题?先给大家初步的总结一下:
- 第一个问题就是 ZooKeeper 的 CP 属性决定写操作延时过高。
分布式有一个 CAP 理论,任何一个分布式的系统都不能同时满足 CAP 三个属性,只能同时满足最多两个属性。 AP 或者是CP,而我们的 ZooKeeper 其实就是一个 CP 的一个分布式的系统,这种属性其实就是仅仅是满足了分区种错性和一致性,但是它牺牲了高可用性。这种属性就决定了写的操作延时是比较高的,因为它要满足一致性就必须要多做一些工作,这个时候可能就会对于写不就不是很友好,它的延迟时间就比较高。
- 元数据过多会延长新 Controller 节点的启动时间,从而造成堵塞。
Controller 这个角色主要是一个总控节点来负责整个集群的管理,比如 做partition 的选主的工作。 但是我们并不能保证 Controller 它一直是存活的,肯定要有一个故障转移的机制。然后如果 Controller 挂了的话,新的 Controller 要起来,这个时候新的 controller 节点要从 zookeeper 复制元数据,如果元数据过多的话,就会延长新的 Controller 节点的一个启动时间啊。
- 主题分区发生变化时,可能会产生大量的 Zookeeper 的写操作,这样也会引起一些阻塞。
比如说一些极端情况,比如说某一个 Broker 节点下线了, broker 里面可能会有 partition leader节点,如果他的 leader 比较多的话,那么他会造成一个问题,Controller 会重新选取新的leader,因为 partition 有且只能有一个 leader 节点。如果 Broker 挂了的话,那么肯定要从别 Broker 节点里挑选一个 follower partition 成为 leader partition ,这样的话就会造成新的分区属性的变化,也就是元数据的变化,Controller 要把这些变化写到 Zookeeper 里。
- zookeeper 增加了运维的成本。
大家能够知道这是ZooKeeper,它的运维成本还是相当高的,需要我们的运维人员对 ZooKeeper 有一个很好的理解,还有很强的一个使用的背景才能够把它运维好,因为它有许多节点的角色,以及在运维过程中需要注意很多事情,所以它无形之中就会增加了运维的成本。
ZooKeeper 的 CP 属性的影响
我们先解释一下 ZooKeeper 的 CP 属性对我们的写是如何影响到我们的写操作的,大家可以看到我们整个的 ZooKeeper 集群是有一个 leader 节点,即四个 follower 节点的。当客户端要写入一个数据的时候,它只能往 leader 上面去写。只有 Zookeeper 的leader 节点负责写,而 follower 节点它是拒绝写操作的,只能承担读的操作。当写的时候,客户端只能向有且唯一的一个 leader 节点上去写数据,然后 leader 节点负责把数据同步到所有的 follower 节点里面去。同时,必须有过半的follower 节点成功的收到数据之后,ZooKeeper 的leader 节点才能够返回客户端写入数据成功。在这个过程中有大量的复制数据复制的过程,这样就会造成写入操作比较慢。也就是说,客户端就会等待 ZooKeeper 的 leader 和半数的 follower 完成写入,才能够完成整个的一个写入的一个过程,也就是说它的一个写造成了一个很长时间的一个阻塞。
为什么 Zookeeper 要这样设计?
Zookeeper 是 CP系统,C指consistency,也就是一致性,什么叫一致性,比如我往Zookeeper leader 里写入 a=1, 我下次从 Zookeeper follower 里读的时候也应该是 a=1,而不是别的数据,所以要做 leader 和 follower 直接的数据同步,但是增加了延迟,甚至会造成超时失败。影响了可用性。
元数据数据过多会延长新 Controller 节点的启动时间。
这里先给大家详细的讲解一下。 Zookeeper 的选主原理是这样的:
当 Kafka 的三个Broker 节点都起来的时候,他们首先第一个工作是向 Zookeeper 注册一个临时节点,他们会同时注册这个一个临时节点,来去抢夺这个临时节点的一个资源。只要有一个 broker 节点成功注册了这个临时节点,那么另外的两个节点就没有办法去创建了,也就说创建失败,那么成功创建这个临时节点的的 broker 会赋予它一个 Controller 的角色,这就是它的一个 Controller 节点的一个选举过程。
然后这会有什么问题呢?其实问题是这样的,当我们的一个 Controller 挂了之后,这个时候两个 Broker 都会去尝试创建临时节点。只要是 Broker 是 active 的,只要 Broker 是活着的,那么它就会永远尝试创建这个临时节点,当 Controller 挂了从而临时节点消失了之后,其它 Broker 节点就会争抢创建 这个临时节点。虽然非 Controller Broker 节点会复制 Controller 节点的元数据。但是新的 Controller 节点一开始必须要与 Zookeeper 上的元数据保持一致才可以,因为必须要以 Zookeeper 上面的元数据为准。
这个时候当新的 Controller 起来后,它的第一个任务就是要从 Zookeeper 上去拉取最新的元数据。如果这个海量的元数据过多的话,就会造成比较长时间的一个阻塞,这个阻塞会造成什么样的问题呢?会造成在 Controller 在这个过程中无法正常的工作,也就是说 Controller 在这个期间它只能从 Zookeeper 复制元数据,其他的一些工作它是无法进行的。 比如说选给 partition 选出新的主,因为有的 partition 的 leader 挂了,要重新选主,这个时候这个工作是做不了的,或者说某一个 Broker 挂了,那么 broker 上面的 partition leader 也要进行故障转移,这个时候 Controller 也做不了,也就是这有一个比较长时间的一个等待过程。这个时候它 Controller 它只能负责元数据的拷贝,除此之外它什么都做不了的。这就是一个很大问题,是在我们的生产环境中会出现这样的问题,而且一旦出现这样问题的话,对我们的影响还是比较大的。因为这个时候 Controller 什么工作都做不了了。
就是说当主题分区发生变化的时候,会因会引发一系列的问题。
大家可以看到可以举一个极端的例子,比如说 Broker0 挂了,那么 Broker 0 上面有 partition leader 的话,那么就会面临着 Controller 要重新为出问题的Partition 选主并把结果上报给 Zookeeper。
可以从图中看到 partition0 和 partition2,因为 Broker0 挂了后,partition0 和 partition2 就已经没有 leader 角色了,必须要从别的 Broker上的 follower partition中去选择一个成为 leader。
具体过程是这样的,因为所有的 broker 都是跟 zookeeper 保持心跳的,比如说如果 Zookeeper 长时间没有去监控到 broker 0 的心跳的话,Broker 0的探活临时节点就会消失(注意不是 Controller 选举竞争的临时节点),Broker 0 临时节点消失之后我们的 controller 节点就会监控到,Controller 根据的元数据就能够就够能够知道 Broker 0 上面负责哪些 partition 的 leader,它就会进行重新的选举以及 ISR 的更新。 所谓 ISR 其实就是一些可用的 partition 副本集合,partition 的 leader和最新的 ISR 会在 Controller 内部会选择,更新完了之后会将这些新的数据元数据更新到 ZK 上面去。 Zookeeper 更新完了之后会向对应的 broker 去发送 LeaderAndIsr,也就是告诉这些 Broker partition 的leader 和 ISR都有变动。比如说看这里 broker 2 它的 P0 follower 被选为leader,那么我们就可以通过 LeaderAndIsr request 告诉他们这两个节点还有 broke 3 的 Broker 2 的 follower,这两个节点对应的副本分别都会变为leader 副本。
但是,这就是在这个过程中就会产生了一个问题。什么问题呢?就是说这个地方也会产生一个 Controller 向 Zookeeper 写的一个过程。如果这个 Broker 下的 partition leader 比较多, 例如 broker 0 的 leader 比较多的话,比如说如果有几个、几十个甚至几百个都不会产生一些问题,如果有几千个甚至上万个的话,那问题就比较大了。 因为元数据的体量就非常大了,这时候就面临着什么呀?面临着刚才我说的问题,就是一个写它是比较慢的了,因为你数据量小,leader 收到数据之后向 follower 节点同步的话,就如果小的话还可以,如果是比较大吞吐量就比较大了。 同时还有 Zookeeper 同步数据的问题,比如说我们这张图可能有两个follower。如何使大于一半的副本同步成功的话,那么就是两个follower 节点都要同步成功。那么整体写操作就是三次。比如说如果我只是向 leader 写的话,那么就写一份就可以了。 但是 leader 它又要向两边儿去写,虽然两边是同步写的,但是它会以最慢的那个数据同步为那个截止时间。所以说这个速度是比较慢的。
好,我们总结一下:
-
Controller 在这个过程中还是无法正常工作的。 比如说某一个 partition leader 挂了,我们要选择新的 partition follower 变成 leader 角色的话,Controller 这时候是无法做这个工作的,因为 Controller 这个时候因为已经丧失了工作的能力了,因为他要去跟 Zookeeper 进行通信来拷贝元数据,Controller 在跟 ZK 读写的过程中, Controller 是无法进行正常的工作的。
-
然后还有一个问题,就是 P0 和 P2 分区对应的主题无法处理消息的收发,也就是说如果我们想读写 P0 和 P2 这两个分区的话,现在所有的客户端不管生产者还是消费者是不能生产和消费 P0和 P2的。当然别的比如说P3、 P4 或者别的主题的分区都是可以的,因为这两个分区P0和 P2正在选主的过程中也就是说两个阻塞。
ZK 它的本身的存在就会增加了运维的成本
-
必须有一半以上的节点正常才能对外提供服务。 也就是说二分之n加一个节点才能提供正常的服务。如果一半的节点不能正常的工作的话,它是拒绝所有的读写的请求的,所以说这是一个很恐怖的一个问题。所以就要求我们的运维人员对 Zookeeper 的理解要非常的深,同时要有很丰富的 Zookeeper 运维经验才可以。
-
Zookeeper 的节点类型是比较复杂的。包括leader、follower还有observer,所以说它也是一个比较大的一个问题,就是它的学习成本是比较高的呃。
-
同时要运维 Zookeeper 集群和 Kafka 集群两套集群,这样肯定会增加我们的一个运维人员的一个工作量,同时会增加事故的发生率,因为一个分布式系统的节点越多,节点种类越多样化,那么出现问题的概率就会越大,这都是成正比的。