# zookeeper **Repository Path**: interview_44/zookeeper ## Basic Information - **Project Name**: zookeeper - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-04-28 - **Last Updated**: 2023-02-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # zookeeper #### 介绍 zookeeper学习了解使用 内存数据模型:znode有持久节点,虚拟节点,顺序节点: 如果你是做元数据存储,肯定是持久节点。 如果你是做一些分布式协调和通知,很多时候是用临时节点,就是说,比如我创建一个临时节点,别人来监听这个节点的变化,如果我断开连接了,临时节点消失,此时人家会感知到,就会来做点别的事情。 顺序节点,在分布式锁里用的比较经典。 客户端跟zk使用长连接:TCP协议,维持session,写入保持顺序一致性。 ZAB协议:2PC,过半ACK+磁盘写日志。 写的时候:顺序一致性,最终一致性。 高性能,commit是纯内存操作。 高并发:单leader写,抗几万QPS是没问题的,过多会打死。 主从架构:leader,follower,Observer。 zookeeper Observer节点:不参与leader选举,不参与ZAB的 follower ACK 的过半同步环节,只是单纯的接受数据,同步数据。所以可以扩展很多台服务器。 zk适合读多写少,适合多部署Observer。写多的时候,会把zk打死,zk扛不住压力,写是一个很大的瓶颈。 -[1、zookeeper工作,与dubbo事例](#1zookeeper工作与dubbo事例) -[2、zookeeper分布式锁](#2zookeeper分布式锁) -[3、zookeeper的分布式协调](#3zookeeper的分布式协调) -[4、zookeeper高可用](#4zookeeper高可用) -[5、zookeeper集群](#5zookeeper集群) -[6、zookeeper集群架构原理](#6zookeeper集群架构原理) -[7、zookeeper如何解决数据不一致](#7zookeeper如何解决数据不一致) ### 1、zookeeper工作,与dubbo事例 ![](./picture/1、元数据、配置信息管理.png) 1、provider启动,上传服务配置到zookeeper。 2、consumer启动,zookeeper推送注册列表到consumer。 3、dubbo注册中心,订阅注册中心上所有的变动,之后通过notify()方法写入到registryCache中,监听者模式。 4、dubbo注册中心,通过前端操作,修改zookeeper里面的节点信息,实现路由监控等功能, ### 2、zookeeper分布式锁 redis分布式锁与zookeeper分布式锁 1、redis锁算法有争议,2、redis键锁,多个节点都有,3、zookeeper,只有一个锁,实现机制健壮清晰。 ![](./picture/2、zookeeper分布式锁.png) 理解了锁的原理后,就会发现,Zookeeper 天生就是一副分布式锁的胚子。 首先,Zookeeper的每一个节点,都是一个天然的顺序发号器。 在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一。 比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。 其次,Zookeeper节点的递增性,可以规定节点编号最小的那个获得锁。 一个zookeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程都会在这个节点下创建个临时顺序节点,由于序号的递增性,可以规定排号最小的那个获得锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。 第三,Zookeeper的节点监听机制,可以保障占有锁的方式有序而且高效。 每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。 Zookeeper的节点监听机制,可以说能够非常完美的,实现这种击鼓传花似的信息传递。具体的方法是,每一个等通知的Znode节点,只需要监听linsten或者 watch 监视排号在自己前面那个,而且紧挨在自己前面的那个节点。 只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,则获得锁。 为什么说Zookeeper的节点监听机制,可以说是非常完美呢? 一条龙式的首尾相接,后面监视前面,就不怕中间截断吗?比如,在分布式环境下,由于网络的原因,或者服务器挂了或则其他的原因,如果前面的那个节点没能被程序删除成功,后面的节点不就永远等待么? 其实,Zookeeper的内部机制,能保证后面的节点能够正常的监听到删除和获得锁。在创建取号节点的时候,尽量创建临时znode 节点而不是永久znode 节点,一旦这个 znode 的客户端与Zookeeper集群服务器失去联系,这个临时 znode 也将自动删除。排在它后面的那个节点,也能收到删除事件,从而获得锁。 说Zookeeper的节点监听机制,是非常完美的。还有一个原因。 Zookeeper这种首尾相接,后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是每个节点挂掉,所有节点都去监听,然后做出反映,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反映。 羊群效应: 多个客户端,同时创建一个目录,多个监听器并行运行,无序的抢锁,导致性能降低,为了避免羊群效应,使用curator框架创建临时节点模式即可。 脑列问题: 客户端A获得了锁,与zk断开连接异常,没有挂掉,但是zk认为客户端A已挂,就把锁释放了,客户端B,获得锁,此时客户端A与客户端B同时持有了分布式锁。 避免这种情况其实也很简单,在slaver切换的时候不在检查到老的master出现问题后马上切换,而是在休眠一段足够的时间,确保老的master已经获知变更并且做了相关的shutdown清理工作了然后再注册成为master就能避免这类问题了,这个休眠时间一般定义为与zookeeper定义的超时时间就够了,但是这段时间内系统可能是不可用的,但是相对于数据不一致的后果我想还是值得的。 当然最彻底的解决这类问题的方案是将master HA集群做成peer2peer的,屏蔽掉外部Zookeeper的依赖。每个节点都是对等的没有主次,这样就不会存在脑裂的问题,但是这种ha解决方案需要使用两阶段,paxos这类数据一致性保证协议来实现,不可避免的会降低系统数据变更的系统,如果系统中主要是对master的读取操作很少更新就很适合了。 ### 3、zookeeper的分布式协调 ![](./picture/3、分布式协调.png) 1、服务A保存一笔订单,发送了消息给MQ,就可以对订单order=1的node注册监听器。 2、对order=1的node更新他的值,finishUpdate,触发事件。 3、订单order=1的node,值发生变化:finishUpdate。 步骤完成后,感知系统B操作成功。 ### 4、zookeeper高可用 ![](./picture/4、分布式高可用.png) zookeeper感知到有一台系统A服务器故障,只能调用另外一台,然后删除原来临时节点,备用节点升级为新的临时节点,然后更新调用者的本地注册列表。 ### 5、zookeeper集群 分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳: 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)。 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)。 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。 zookeeper集群:leader-follower 写的话就leader负责,读就follower负责。 CAP中的CP,強一致性,挂掉不可用(非高可用)。 服务发现非常快,注册服务快,同步快,更新服务列表快,秒级可以。 不太适合大量的集群,最多几百个服务实例。 ![](./picture/6、zookeeper集群.png) ### 6、zookeeper集群架构原理 集群启动:恢复模式,leader选举(过半机器选举机制) + 数据同步。 消息写入:消息广播模式,leader采用2PC模式的过半写机制,给follower进行同步。 崩溃恢复:恢复模式,leader/follower宕机,只要剩余机器超过一半,集群宕机不超过一半的机器,就可以选举新的leader,数据同步。 ![](./picture/5、zookeeper集群架构原理.png) zab协议,划分集群角色,主从结构,划分leader与follower角色, leader接受到客户端写请求, leader会往follower发送proposal,follower收到proposal就会写到磁盘文件,写完发送ack给leader,超过1半的follower接受到了之后,执行commit。 commit把proposal写到znode里。 所以用到了两阶段提交。 ### 7、zookeeper如何解决数据不一致 ![](./picture/7、zookeeper如何保证数据一致.png) 第四步挂掉,leader挂掉。 1、选择一台根据ZID最大的机器作为leader。 2、其他follower跟他进行同步,给每个follower准备一个队列,然后把所有的proposal发给新leader,只要过半的follower都返回ack,就会发送commit给那个新leader。 3、commit就是把这条数据加入内存中的znode树形结构,然后就对外可见了,也会去通知一些监听znode的人follower与leader同步了,就会加入leader的同步follower队列中,过半的follower同步完了,就可以对外继续服务。 第一步到第二步中间挂掉。 如果一个leader自己刚把一个proposal写入本地磁盘日志,就宕机了,没来得及发送给全部的follower,此时新leader选举出来,他会的epoch会自增长一位proposal,高32位是2,低32位是继续自增长的zxid。 然后老leader恢复了连接到集群是follower了,此时发现自己比新leader多出来一条proposal,但是自己的epoch比新leader的epoch低了,所以就会丢弃掉这条数据