2021-03-25 通俗易懂剖析Zookeeper

       zookeeper是一个非常好用的产品化工具,是Apache的顶层产品,应用广泛。其应用范围、应用方式、架构逻辑和组成可以给我们具体的应用带来很多启示。

       其实在网上也有了很多关于zookeeper的介绍,但总是相对零散,不够通俗和全面,在看了ZooKeeper的各种材料后,尝试总结一下ZooKeeper到底是一个什么东西。本文主要是从zookeeper的原理、设计及应用场景方面分析zookeeper的技术逻辑,尝试从更通俗、全面的角度进行zookeeper的技术解读,方面大家理解,并进行应用参考和技术决策。

 一,Zookeeper简介

        Zookeeper是一个支持高吞吐的分布式协调系统(ZooKeeper is a distributed, open-source coordination service for distributed applications)。这句话里有几个概念:高吞吐、分布式、协调系统。这些概念到底在说什么?
        首先,来看“分布式”。所谓分布式,就是分布式部署,概念大家都不陌生,没什么好解释的。分布式部署通常不是几台机器,而是体量比较大的集群。在遇到集群需要更新配置、数据的时候,运维同学一个个节点手工ssh上去更新?不存在的。所以要考虑批量地管理分布式集群、协调事务就是个问题。 zookeeper就是为解决这些问题而生的产品。虽然不太恰当,这里可以认为zookeeper就是一个分布式集群的管理中心。有意思的是,zookeeper本身也是分布式部署的;
        其次,来看“高吞吐”。要分清,这里的高吞吐是对zookeeper的来说的,并不是指zookeeper管理的集群。因为在通常情况下zookeeper管理的集群都比较庞大,需要同集群里的多个节点进行通讯,因此需要比较大的吞吐量。那么,zookeeper的高吞吐是怎么做到的呢?其实也很简单,像redis一样把数据放在内存里(虽然内部数据结构仍然是树形目录的文件系统),而且由于它是分布式的,有好多个zookeeper server提供服务(zookeeper也有数据存放在硬盘里,基本就是为备份而存储的数据快照以及日志数据);
         最后,来看“协调“。所谓协调,就是分布式协调技术。那么什么是分布式协调技术?分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果。这时,有人可能会说这个简单,写一个调度算法就轻松解决了。说这句话的人,可能对分布式系统不是很了解,所以才会出现这种误解。如果这些进程全部是跑在一台机上的话,相对来说确实就好办了,问题就在于他是在一个分布式的环境下。zookeeper就是解决这个问题的工具,就是这个“协调器”,实际就是我们常提到的--。这个锁保证共享资源在某一时刻由某个进程独占。这就保证了分布式环境下多进程对临界资源的访问。这个分布式环境下的锁就叫分布式锁
        所以,通俗来说,zookeeper就是一个管理分布式集群的另一个分布式集群协调工具,管理效率高,而且开源。从技术层面概括,ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。
        要实现像zookeeper这么一个高效的分布式锁,可不是像在单机上实现任务调度原语这么简单。因为分布式系统靠网络连接,而网络是不可靠的。如果某个进程X访问了临界资源,返回failed。那结果一定是fail么?恐怕不一定,也有可能执行已经成功了;再例如服务A和服务B都去访问服务C,服务A先发起请求,你一定能保证是服务A的请求先到达么?

        那么zookeeper是怎么实现的呢?

二,Zookeeper原理

在说zookeeper的原理之前,我们先来看看zookeeper内部的数据结构

1,ZooKeeper的数据结构

        ZooKeeper的数据结构是一棵树,类似于unix的文件系统,树上是一个个的节点,每个节点又可以包含很多个、很多级的子孙节点,具体示意图如下:此时,忽略图中节点框中的内容,这就是一个完整的ZooKeeper内部的数据结构模板,这里每个节点都叫做Znode。Znode一共有四种:
(1)PERSISTANT--持久化目录节点
(2)PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
(3)EPHEMERAL-临时目录节点.
(4)EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

        这四种主要分成两类,持久化节点和临时节点。顾名思义,持久化节点就是持久保存的节点,一旦创建了此节点,除非主动删除,否则节点一直存在;临时节点就是Client客户端临时创建的节点,一旦会话结束,则节点会自动删除。而所谓的SEQUENTIAL,只是持久化和临时节点按顺序存放而已。

2,Znode的内部结构

        Znode内部包括数据和属性。
(1)数据
        ZooKeeper的机制保证了读写操作的原子性,即ZooKeeper不支持部分read和部分write。此时注意,ZooKeeper并不是一个数据库,不能将ZooKeeper作为一个数据库使用。ZooKeeper规定每个节点存储数据的大小不能超过1M。而且数据量大了肯定会影响性能,所以,一定要尽量少的往节点存储数据。
(2)属性
        每个Znode都有各种属性,客户端可以通过get命令获取各种属性。下面列一下比较重要的几个属性:


get /module1/app2  //节点
app2  //数据
cZxid = 0x20000000e  //事务ID
ctime = Thu Jun 30 20:41:55 HKT 2016  //事务更新时间
mZxid = 0x20000000e  //节点ID
mtime = Thu Jun 30 20:41:55 HKT 2016 
pZxid = 0x20000000e 
cversion = 0 
dataVersion = 0 
aclVersion = 0 
ephemeralOwner = 0x0 
dataLength = 4 
numChildren = 0

请留意这几个属性,在下文中会用到。


3,Znode的基本操作

        ZooKeeper对节点的基本操作主要分成以下几种:

create:创建节点
delete:删除节点
exists:判断节点是否存在
getData:获得一个节点的数据
setData:设置一个节点的数据
getChildren:获取节点下的所有子节点

        这里的exists、getData、getChildren是属于read行为,其余的都是write。这个很容易看出来。
        ZooKeeper是分布式的C/S结构,但为了便于理解问题,我们先简化ZooKeeper此时只有一个Server和一个Client--Client客户端在向Server进行read行为时,可以选择是否设置watch。那么什么是watch呢?其实相当于客户端在特定的Znode上注册了一个回调,一旦该节点的数据发生变化--假如是改动了数据内容,此时节点就触发这个回调异步通知客户端来重新拉一遍数据。
        那么,这个回调就叫Watcher,多个客户端的回调就组成了节点的Watcher List。这里需要注意的是,这里数据更新的操作不是靠节点把数据推(push)过去的,也不是靠节点主动来(pull)的,而是先通知后拉取的,这就是ZooKeeper独特的推拉(push-pull)机制。为什么不直接推或者等着Client主动来拉呢,这个大家可以思考一下?
        这里简单回答一下,Znode的这种数据组织方式是如何支撑ZooKeeper实现基础功能--分布式锁的呢?这种数据组织方式有何优势之处?
        答:借助临时顺序节点(EPHEMERAL_SEQUENTIAL)实现分布式锁。多个Client访问共享资源,每个Client都注册了临时顺序节点,server就按顺序通知访问,完成后临时节点销毁。用这种方式实现多Client对共享资源的无冲突访问 ,这就是分布式锁实现的基本原理。
这种数据组织方式奠定了分布式锁可以实现的结构基础。

 

三,ZooKeeper的基本架构


1,基本结构
        上文介绍了ZooKeeper运行的基本原理,也使用了一个Server + 一个Client的简化方式,并不能确切反应ZooKeeper分布式集群的基本结构,下面就详细看一下ZooKeeper的分布式架构:

        图中,上部分就是zookeeper的服务,图中有五个节点。下半部分是Client,也可以简单认为是zookeeper所管理的分布式集群。
        先看下半部分,主要是一些被管理的Client,这些Client配合zookeeper service完成对分布式集群的管理。相邻的一个或多个Client连接到某个Server,不存在一个Client连接多个Server的情况;
        再看上半部分的zookeeper Service,其中包括5个server,也可以是3,7,9,11等等,为什么是奇数个,这个跟zookeeper的选举机制有关,下文会说到。这里5个server中有一个是Leader,剩下4个叫做Follower,可以看出所有的Follower都与Leader之间进行交互,Follower彼此之间无数据交互;
        根据以上的交互原理图,我们大概能知道zookeeper整体的架构。就是通过Client与Server(Follower),然后Server(Follower)与Server(Leader)之间的交互来完成的。这里的交互是一个限定词,所谓的交互无非是读(Read)和写(Write)。在Client发起读请求时,向就近的Server发起请求即可,可直接获取相关数据(因为ZooKeeper的Server集群是数据一致的,但注意这里的一致不是强一致性,而是最终一致性,后续会说到)。但是写操作请求则必须通过Leader进行,即由Follower将写请求转发给Leader,写操作完成后,相关数据由Leader同步到各个Follower。

2,一致性原理
        这里的一致性原理包括一致性协议和数据的一致性。
        基于上文中提到的Zookeeper的基本结构,首先来思考一个问题:如果ZooKeeper管理的集群OK,但ZooKeeper本身Down掉了呢?
        其实就是Leader凉了,剩下的Server们要重新选举一个Leader出来,这就是ZooKeeper的Leader Election过程。那么,这个过程是如何实现的呢?
     (1)ZAB协议
        zookeeper实现数据一致性的核心是ZAB协议(ZooKeeper Automatic Broadcast, Zookeeper原子消息广播协议)。这个协议需要满足以下需求:

a.集群在半数以下节点宕机的情况下,能正常对外提供服务;
b.客户端的写请求全部转交给Leader来处理,Leader需确保写变更能实时同步给所有Follower;
c.Leader宕机或整个集群重启时,需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交,确保丢弃那些只在Leader服务器上被提出的事务,并保证集群能快速恢复到故障前的状态。

        Zab协议有两种模式, 崩溃恢复(选主+数据同步)和消息广播(事务操作)。任何时候都需要保证只有一个主进程负责进行事务操作,而如果Leader崩溃了或者Server不能与Leader保持正常连接,就需要迅速选举出一个新的Leader。Leader的选举机制与事务操作机制是紧密相关的。下面详细讲解这三个场景的协议规则,从细节去探索ZAB协议的数据一致性原理。
     (2)Leader Election
        Leader选举是ZooKeeper中最重要的技术之一,也是保证分布式数据一致性的关键所在。在选举时,各个Server进入LOOKING状态,选举的过程就是投票,每个Server都广播自己的投票内容,内容主要包括两个字段:myId,cid(上文属性中的两个字段),即自身ID与事务ID。
        选举过程:

a.设置状态为LOOKING,初始化内部投票Vote (id,zxid) 数据至内存,并将其广播到集群其它节点。节点首次投票都是选举自己作为leader,将自身的服务ID、处理的最近一个事务请求的ZXID(ZXID是从内存数据库里取的,即该节点最近一个完成commit的事务id)及当前状态广播出去。然后进入循环等待及处理其它节点的投票信息的流程中;
b.循环等待流程中,节点每收到一个外部的Vote信息,都需要将其与自己内存Vote数据进行PK,规则为取ZXID大的,若ZXID相等,则取ID大的那个投票。若外部投票胜选,节点需要将该选票覆盖之前的内存Vote数据,并再次广播出去;同时还要统计是否有过半的赞同者与新的内存投票数据一致,无则继续循环等待新的投票,有则需要判断leader是否在赞同者之中,在则退出循环,选举结束,根据选举结果及各自角色切换状态,leader切换成LEADING、follower切换到FOLLOWING;

上图是单个Server上在进行选举时的逻辑流过程,多个Server按照此过程相互配合完成整个集群的选举过程,具体多Server配合选举的方式如下:

        选举过程的算法细节可参照FastLeaderElection.lookForLeader(),主要有三个线程在工作:选举线程(主动调用lookForLeader方法的线程,通过阻塞队列sendqueue及recvqueue与其它两个线程协作)、WorkerReceiver线程(选票接收器,不断获取其它服务器发来的选举消息,筛选后会保存到recvqueue队列中。zk服务器启动时,开始正常工作,不停止)以及WorkerSender线程(选票发送器,会不断地从sendqueue队列中获取待发送的选票,并广播至集群)。更深一步的细节不再赘述。
     (3)数据同步
         这里说一下这个数据同步过程,解释上文中提到的最终一致性的问题。
        在完成Leader Election之后,Leader需要向Follower发送同步数据消息,保证ZooKeeper集群数据保持在一致的状态。其实具体的过程就是一个数据的Broadcast过程。什么是 Broadcast 呢?简单来说,就是 Zookeeper 常规情况下更新数据的时候,由 Leader 广播到所有的 Follower。我们顺便以Client的请求为例来描述其过程,具体如下:

a. 客户端发出写入数据请求给任意Follower;
b. Follower 把写入数据请求转发给 Leader;
c. Leader 采用二阶段提交方式,先发送 Propose 广播给 Follower;
d. Follower 接到 Propose 消息,写入日志成功后,返回 ACK 消息给 Leader;
e. Leader 接到半数以上 ACK 消息,返回成功给客户端,并且广播 Commit 请求给 Follower;

        上图中,如果是故障恢复,那就没有Request,数据是由Leader从内存快照(上文中说了,ZooKeeper的数据都存放在内存)中取得,然后进行数据同步。
        从这个过程里可以看出来,ZooKeeper既不是强一致性,也不是弱一致性,但是ZooKeeper保证了集群的最终一致性。

        可以看出,ZooKeeper在使用中多次提到了”过半“的策略,该策略是zk在A(可用性)与C(一致性)间做的取舍,也是zk具有高容错特性的本质。但是,ZooKeeper本质上是一个分布式协调系统,目的就是为了保证数据在管辖服务之间的一致性。所以,请记得ZooKeeper仍然是CP而不是AP的,这也是阿里的Dubbo不使用ZooKeeper而转向Nacos作为服务注册与发现组建的原因(注:关于Dubbo的注册与发现,请参考:https://mp.weixin.qq.com/s/RML_nofuh0vIagQc6To54A


四,ZooKeeper的应用场景

        作为一个基础分布式锁的分布式协调系统,ZooKeeper最常用的场景自然就是作为分布式锁来应用,这也是当初Zookeeper作为Hadoop产品的一个原因。
 1,分布式锁
        分布式锁主要得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

(1)所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁;
(2)控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。Zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

2,数据订阅与分发
         发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
3,负载均衡
        这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。
        消息中间件中发布者和订阅者的负载均衡,linkedin开源的KafkaMQ和阿里开源的metaq都是通过zookeeper来做到生产者、消费者的负载均衡。
4,分布式通知/协调
        ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。
        除了以上的应用场景,ZooKeeper还有其他的应用场景,在此不再赘述。相信大家在看了ZooKeeper的这些介绍后,在具体的技术选型和应用中大概就能比较明确地对是否应用、如何应用ZooKeeper做出决策了。


五,ZooKeeper的使用

         ZooKeeper有活跃的社区和丰富的文档,具体的安装、配置、使用方法就不再赘述了。大家在使用时可以参考本文开始提到的经典实践中的几篇介绍,也可以去社区寻求支持,最重要的是去读官网的Manual。

 

感谢各位花时间阅读此文,如有不合理之处,敬请指正。

参考:
http://zookeeper.apache.org/
http://zookeeper.apache.org/doc/current/index.html
https://blog.csdn.net/gs80140/article/details/51496925
https://blog.csdn.net/WeiJiFeng_/article/details/79775738
https://www.cnblogs.com/tommyli/p/3766189.html
https://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650712100&idx=4&sn=94ea4679c1565f69023ef1b63e6ca5ab

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ango_Cango

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值