- 作者:老汪软件技巧
- 发表时间:2024-09-30 21:01
- 浏览量:
对应视频讲解:
…
我们讨论一个重要话题,就是 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 集群两套集群,这样肯定会增加我们的一个运维人员的一个工作量,同时会增加事故的发生率,因为一个分布式系统的节点越多,节点种类越多样化,那么出现问题的概率就会越大,这都是成正比的。