Zookeeper真的已经过时了吗? 您所在的位置:网站首页 xdevios不能用了吗 Zookeeper真的已经过时了吗?

Zookeeper真的已经过时了吗?

2024-07-12 12:16| 来源: 网络整理| 查看: 265

  主要是想整理和回顾一下当先Zookeeper的问题,因为昨天在与同事聊天的过程中,有很多人比较极端的认为当下的ZK已经要退出历史的舞台了,现在已经没有用武之地了。但是笔者觉得事情不是那么的绝对,或者说就算是ZK要退出历史舞台,我们也应该是知道为什么,而不是单纯的现在很多项目不使用了,我们就一棒子打死,这不是一个技术人应该有的态度和做事的风格。

一、为什么会出现ZK

  因为本文是包括一部分回顾的意思在的,所以第一章来回顾为什么会出现ZK就太合适不过了。很多人对ZK的认识就是一个分布式协调中间件,是一个在分布式时代下的产物。提到事务可能我们最熟悉的理论就是ACID,这个理论在关系型数据库中被使用的比较多。但是在分布式场景中可能CAP和BASE理论是应用的比较广泛,因为在分布式场景中又多出了很多变量,比如网络,未知状态等。

  BASE可以说是在CAP之后,结合实际应用场景作出的一种分布式妥协理论,但是基本上是与CAP的思路和范围约束是保持一致的,下面我们通过一个表格进行对比和回顾一下:

C (Consistency) A (Avaliablity) P (Partition tolerance) 一致性:在分布式环境下多个副本的状态要保持一个一致的状态 可用性:达到一个可用的状态,在有限时间内返回需要的结果 分区容错:在出现网络分区故障的时候,仍然能保证一执行和可用性,除非是整个网络都出现了故障 E (Eventually consistent) BA (Basically Avaliable) S (Soft state) 最终一致性:系统中所有的副本在经过一段时间后会达到一个一执行,不要求实时的 基本可用:服务是可用的,但是可能在时间或者返回结果上做一些妥协 软状态:允许在事务过程中出现中间状态,但是中间状态不会影响整体可用性,允许在状态转换中出现一定的延迟

那么在理论之上就出现了一些分布式事务协议,下面首先来看一下2PC和3PC,其实他们的区别不大,我这里用更加通俗的语言进行说明。其实2PC我们都能理解,但是两者一出现,其实我们只要通过分析两者的不同,就能更好的记住和理解了:

2PC :

1.协调者给参与者发送事务内容,等待参与者的预执行结果

2.如果参与者收到了所有参与者反馈的Yes,然后向参与着下发提交的命令,参与者提交各自的任务,并且释放事务资源

  如果参与者收到了一个No ,或者是等待一个超时,那么下发回滚的命令,参与者回滚各自的任务,释放资源事务资源

存在问题:

1.单点问题:如果在第一阶段参与者都进行了事务操作,并且将结果上报给了协调者,但是这个时候协调者挂了,那么所有的参与者会一直阻塞

2.阻塞时间较长:如果出现单点问题,或者是在等待协调者命令的时候出现了网络问题,那么当前的参与者就会一致阻塞

3.如果在发送提交或者是回滚指令的过程中,其中一个参与者无法接受到指令,那么整个分布式系统中就出现了数据不一致的问题

 

3PC :只是在2PC的基础上进行了一些优化,但是并没有完全解决2PC的问题

1.协调者向各参与者发送事务内容,询问是否可以执行

2.如果所有参与者都反馈可以执行,向各参与者发送执行的命令

  如果有参与者反馈NO,或者是等待超时,向各参与者发送放弃事务执行的指令

  如果此时协调者挂了,那么没有收到明确消息的参与者会自动终止当前事务

3.各参与者执行事务之后,向协调者反馈是否提交

   如果所有参与者都反馈Yes,那么协调者向所有的参与者发送提交的指令,参与者收到执行进行提交操作,释放事务的资源

   如果收到一个No或者是超时,那么会向所有参与者下发回滚的指令,释放资源。

存在问题:

1.在阶段3也可能出现协调者挂了的场景,或者是给各参与者下发提交或回滚指令的时候,参与者没有收到

  那么针对这种情况,参与者不会一直阻塞等待,而是会在超时之后,直接提交本地事物

 

2PC与3PC的对比:其实3PC的优化点主要是为了减少阻塞的时间,特别是在出现单点故障的时候,但是因为在3阶段的参与者最终是可以直接提交本地事物的,所以还是会产生数据不一致的问题。

 

了解过Zookeeper的人应该知道,ZK并没有直接采用Paxos协议,而是自己开发了ZAB的协议,这里也简单的聊一下ZAB协议:

ZAB对于每一个写请求,会在每个Zk节点上保持一个事务日志,同时再加上定期的将内存的镜像刷新到磁盘来保证数据的一致性和持久性,以及宕机可恢复

二、Zookeeper目前为什么不那么受欢迎了?

  ZK是最早被大众所接受的分布式协调中间件,应该说功能范围比较的宽泛,所以使用的场景应该也是非常的多。但是慢慢的随着集群规模的扩大和使用场景的逐渐复杂,发现在很多场景中好像ZK的表现不是那么的出色,这也许就是某些人觉得ZK的时代已经过去了的原因吧,下面从多个角度来聊一下ZK的使用。

  当下的微服务是非常的流行,随之而来的就是服务治理和服务发现的需求,在很多实际场景中ZK都是被用来做为注册中心使用,进而提供服务发现的能力。首先我们知道ZK是基于CP的,也就是说在出现网络分区或者是数据不一致的情况下,就会牺牲A。但是如果在服务发现的场景中,比如数据获取到的数据不是最新的,或者出现了网络分区,但是可以与同机房的服务实例进行通信,其实这种情况都是可以容忍的啊,不一定要直接牺牲其可用性啊。所以说如果是在服务发现的场景中,可能基于AP的设计会比较好接受,因为毕竟服务的通信不能因为注册中心的状态而受到影响,注册中心不可用,并不代表着服务不可用。而Zookeeper就是基于CP实现的,所以这应该算做它不受欢迎的一个原因吧。

  第二个原因应该就是ZK的处理能力了,特别是近些年,流量的增长十分的迅速而且是呈指数级的增长,作为服务发现中心的ZK要面临的写请求的压力非常大,而带来的表现也不是特别的理想。这个就是与ZK的设计实现有点关系了,下面我们从ZK对消息的处理原理上来说明一下,在什么情况下ZK会表现的不理想。

  首先我们知道ZK的一致性协议是在Paxos的基础上实现的,是一种主备的模式。在任意时刻只能存在一个Leader,所有的写请求都需要有Leader下发,然后根据Quorum机制完成写请求。而且ZK还是一种严格顺序的强一致性,也就是说先提交的Proposal如果没有被处理完,那么后面提交的就一定要等待。所以说在微服务规模逐渐扩大的场景中,如果只有单一进程来处理整个集群的写请求,那么就一定会在一定程度上影响写的性能。 

  还有一个原因可能就是在服务状态变化的过程中,ZK会把变化的过程也都进行持久化的记录,这无形中就增加了很多的操作,但是对于一个服务发现的组件来说,其实并不关心中间的状态变化,而只需要知道实时的服务列表就可以了,而不需要知道变化的过程和历史的记录。所以ZK的这部分能力也是影响写效率的一个重要因素。

  最后就是ZK的健康检查机制,是利用Zk的Session活性Track机制以及结合Ephemeral ZNode的机制,简单的说就是将服务的健康检测绑定在ZK对于session的健康检测,或者说是绑定在TCP长连接的探活上了,但是探活OK对应的服务就一定是OK的吗?所以这个算是ZK作为服务健康检测来说,比较大的一个软肋,在这点上同为基于CP实现的Consul就显得比较灵活一些。 

三、ZK中leader挂掉之后的疑问?

  这个小节是我在看在《从Paxos到Zookeeper 分布式一致性原理与实践》的时候有的疑惑,后来也去查了一些源码的资料。

具体问题是这样,在一个proposal被广播后,leader会等待接受follower返回的ack消息,如果超过半数的follower返回ack,那么leader就会提交这个proposal,同时本地也会提交。我的疑问就在这里,如果在leader提交当前proposal的时候挂了,那怎么保证呢?

而基于ZAB协议我们知道,ZAB会保证以下两条原则:

1.已经被处理的proposal不能丢

2.没有被commit的proposal需要被丢弃

 

这里我自己回答一下我的疑问:

  首先如果是在本地提交之后就立刻挂了,那么follower会收到提交的消息吗?这个我查了一下zk的源码,具体的代码位置是在Leader#tryToCmooit方法中

} else { p.request.logLatency(ServerMetrics.getMetrics().QUORUM_ACK_LATENCY); commit(zxid); inform(p); } zk.commitProcessor.commit(p.request); if (pendingSyncs.containsKey(zxid)) { for (LearnerSyncRequest r : pendingSyncs.remove(zxid)) { sendSync(r); } }

commit方法就是提交的操作,在这里会将提交的消息先发送给所有的follower

/** * Create a commit packet and send it to all the members of the quorum * * @param zxid */ public void commit(long zxid) { synchronized (this) { lastCommitted = zxid; } QuorumPacket qp = new QuorumPacket(Leader.COMMIT, zxid, null, null); sendPacket(qp); ServerMetrics.getMetrics().COMMIT_COUNT.add(1); }

而zk.commitProcessor.commit就是leader自己的提交操作,这样在一定程度上就能够保证消息在leader节点上被正确的发送出去。

但是这样还是不足以保证follower一定能够执行到这个提交的消息。

 

到这里还是有一些疑惑了,记得在书中有这样一段话,也许有很多人和我一样当时不是很理解吧“

由于所有提案被 COMMIT 之前必须有合法数量的 follower ACK,即必须有合法数量的服务器的事务日志上有该提案的 proposal,因此,zxid最大也就是数据最新的节点保存了所有被 COMMIT 消息的 proposal 状态。

这段话怎么理解呢?比如一个proposal在leader提交之后,leader挂掉了,但是集群内的其他follower并没有收到并且执行对应的commit。这之后就要进行leader选举了,此时在所有的follower上面这个proposal都是没有被提交的状态。

按照zk的选举原则,一定会选出一个zxid最大的follower作为新的leader,那么此时这个follower上面一定有当前的这个proposal,并且在日志中一定会记录这个proposal对应的ack,此时当前的leader就会在同步阶段与其他follower询问,当前的proposal是否有ack的日志记录

那么我们知道,之前的旧leader一定是收到了超过半数follower的ack,才会本地提交的。所以这次新的leader发送询问请求一定会得到超过半数follower的回答,包含了ack的记录,那么此时就可以确定当前的proposal是可以被提交的。

同样,如果一个proposal在旧的leader中没有被commoit,那在新的leader 进行询问的时候,一定是得到少于半数的ack回答,那么新的leader就会丢弃掉这个proposal。

四、那现在Zookeeper真的不能用了吗?

  首先来说结论:肯定不是的。

  通过上一节的说明其实可以知道,ZK只是在服务发现的场景中表现的不是那么的出色,但是这与其设计实现原理有关系,因为它根本就不是为高并发这种场景所准备的,它的可用性要求以及持久化等特性只是在服务发现的场景中被过度的放大了。但是在粒度比较粗的分布式锁,分布式选举,TPS不高的分布式数据同步等场景中还是非常适合的。而这些需求在大数据,离线任务的相关业务领域中比较突出,因为在大数据领域比较讲究分割数据集,并且大部分时间分任务多进程/线程去并行的处理这些数据集,但是最终还是需要一个组件来协调这些计算结果的,那这就比较适合ZK来实现。所以说在选取技术组件的时候应该更多的考虑到业务的实现场景,应该更加合理的进行SLA的评估之后再做决定。

五、ZK的可应用场景 数据发布/订阅:将配置信息统一配置,各服务通过客户端watch的方式来获取配置内容,并跟踪配置变化 命名服务:zk提供的可以在某一个节点下顺序的创建子节点,返回带有顺序标识的节点名称,可以用作全局唯一ID,但是缺点也是不具备业务属性 Master的选举:所有客户端在某一个节点下面创建临时节点,只有一个客户端可以创建成功 分布式锁:X锁是在某一节点下创建一个临时节点,S锁是在某一节点下顺序的创建节点,只是S锁为了避免羊群效应,根据读和写的不同逻辑,所以需要watch比自己需要小的那个节点即可 分布式队列:与S锁的实现机制相同,都是在一个节点下顺序的创建临时节点


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有