相信大家一定注意到了,重量级消息中间件Kafka已经发布了2.8版本,正式解除了对Zookeeper的依赖。它背后的设计理念是什么?仅仅是为了减少外部依赖吗?答案显然没有那么简单,让我慢慢来吧。在回答why之前,我觉得非常有必要解释一下Zookeeper的经典使用场景。一、Zookeeper的经典使用场景Zookeeper伴随着大数据和分布式领域的兴起。大数据中一个很重要的问题就是如何使用很多便宜的机器来实现可靠的存储。所谓便宜的机器,就是出现故障的概率很高,但是单台机器的成本也很低。在分布式领域,希望用多台机器组成集群,将数据存储在多台机器上(副本)。为了便于数据的一致性,一般需要从一个复制组中选出一个master节点用户来处理数据的读写,其他节点从master节点复制数据。当master节点宕机时,需要自动重新选举,实现高可用。在上面的场景中,有一个很重要的功能Leaderelection,如何选举出一个master节点,并且支持master节点宕机后自动触发重选,实现主从自动切换和高可用。利用Zookeeper提供的临时时序节点和事件监听机制,很容易实现Leader选举。上面的t1和t2可以理解为一个组织中的多个成员可以提供相同的服务,但是为了达到冷备的效果(即只有一个成员同时对外提供服务,我们称之为Leader,当Leader宕机或停止服务后,组织内其他成员重新竞争Leader,再继续对外提供服务)。如上图所示,Zookeeper部署在集群中,可以有效避免单点故障,并提供集群内数据的强一致性。当成员需要竞争Leader时,用Zookeeper的实现套路是创建两个子节点到Zookeeper中的一个数据节点(例子中的/app/order-service/leader)节点,它们是顺序的临时节点。客户端判断创建的节点序号是否为/app/order-service/leader中序号最小的节点,如果是则成为leader对外提供服务;如果序列号不是最小的,则从其预先注册的节点中删除该事件,一旦Leader代表的进程宕机,与Zookeeper的会话失败,与其关联的临时节点将被删除。Leader创建的节点一旦被删除,将通知其后继节点,从而再次触发选举,新的Leader将继续对外提供服务,保证优质服务的高可用。回头看上面的场景,在Zookeeper的帮助下,很容易实现选主,为提高应用的可用性带来便利。它主要利用了Zookeeper的几个特性:临时节点临时节点与session相关联,针对这个临时节点的一个session在一次创建结束后会自动删除,无需应用端手动删除。顺序节点事件机制借助事件机制,Zookeeper可以及时通知其他存活的应用节点,重新触发选举,实现主从自动切换变得非常简单。2.Kafka对Zookeeper的迫切需求Kafka中有很多leader选举。熟悉Kafka的朋友应该都知道,一个topic可以有多个分区(datashard),每个datashard可以配置多个副本。如何保证多个副本间分区数据的一致性成为迫切需要。Kafka的实现套路是一个partition的多副本,从中选出一个Leader来承担客户端的读写请求,slave节点从master节点复制内容,Leader节点根据数据写入成功做出决定在副本中。判断写入是否成功。Kafka中topic分区分布示意图:所以这里需要进行Leader选举,基于Zookeeper可以轻松实现。从此,一拍即合,开始了“蜜月之旅”。3、ZookeeperZookeeper的致命弱点是集群部署。只要集群中有一半以上的节点存活,就可以提供服务。比如3个节点的Zookeeper,允许1个Zookeeper节点宕机,集群仍然可以提供服务;1个节点的Zookeeper允许2个节点宕机。但是Zookeeper的设计是CP模型,即要保证数据的强一致性,必须在可用性方面做出牺牲。Zookeeper集群中也有所谓的leader节点和slave节点。leader节点负责写,leader和slave节点可以接受读请求。但是Zookeeper内部节点选举出来后,整个Zookeeper就不能对外提供服务了。当然正常情况下选举会很快,但是异常情况就不好说了,比如Zookeeper节点上fullGc,这时候影响会是毁灭性的。如果Zookeeper节点频繁发生FullGc,此时与client的session会超时。由于此时无法响应客户端的心跳请求(StopWorld),因此与会话关联的临时节点将被删除。注意,此时,所有临时节点都会被删除,Zookeeper依赖的事件通知机制将失效,整个集群的选举服务将失效。从高可用的角度来看,Kafka集群的可用性不仅取决于自身,还受到外部组件的制约。从长远来看,这显然不是一个优雅的解决方案。随着分布式领域相关技术的不断完善,去中心化的思想逐渐兴起,取消Zookeeper的呼声也越来越高。在这个过程中,出现了一个非常优秀的算法:Raft协议。Raft协议的两个重要组成部分:Leader选举,日志复制,日志复制为多副本提供数据的强一致性,还有一个显着的特点就是Raft节点是去中心化的架构,不依赖于外部组件,而是作为协议簇嵌入到应用程序中,也就是说,它与应用程序本身集成在一起。以KafkaTopic的分布图为例,引用Raft协议的示例图如下:关于Raft协议,本文不打算深入讨论,但为leader的选举提供了另一种可行的方案,而且不需要依赖第三方组件。为什么不好玩呢?所以最终Kafka在2.8版本正式放弃了Zookeeper,拥抱了Raft。
