美文网首页
ZooKeeper基础

ZooKeeper基础

作者: 伊凡的一天 | 来源:发表于2019-09-29 14:26 被阅读0次

    ZooKeeper是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题。

    ZooKeeper本质上是一个分布式的小文件存储系统,提供基于类似文件系统的目录树方式的数据存储。并且可以对树中的节点进行有效管理,从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。诸如:统一命名服务、分布式配置管理、分布式消息队列、分布式锁、分布式协调等功能。

    1. 基本概念

    角色

    一个ZooKeeper集群中的机器角色分为3种:Leader,Follower和Observer。

    • Leader:负责整个集群的写操作,保证集群内事务处理的顺序性。并且负责与所有的 Follower进行内部的数据同步。
    • Follower:用于接收客户端读请求并向客户端返回结果,对于写请求转交给Leader。并且在选举过程中参与投票。
    • Observer:Observer角色不参与投票(Leader写数据时Follewer需要返回ack,而Observer则不需要),其它功能与Follower相同。引入Observer角色的目的是增加ZooKeeper集群的吞吐量。如果单纯的增加Follower,那么ZooKeeper集群的写能力会大大降低(ZooKeeper写数据时Leader会同步给Follower),因此通过引入Observer角色,使得ZooKeeper集群在写能力不降低的情况下,大大提升了读能力。

    会话

    zookeeper 中客户端启动时会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期就开始了,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向服务器发送请求并接受响应,还能够接收来自服务器的 watch 事件通知。

    节点

    Zookeeper树中的每个节点被称为Znode。和文件系统的目录树一样,Zookeeper树中的每个节点可以拥有子节点。Znode具有原子性操作并且Znode的数据存储大小有限制。

    每个Znode由3部分组成:

    1. stat:此为状态信息,描述该Znode的版本,权限等信息
    2. data:与该Znode关联的数据
    3. children:该Znode下的子节点

    Znode有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。

    • 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当让也可以手动删除。临时节点不允许拥有子节点
    • 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作时,它们才能被删除。

    Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。这样便会存在4种类型的Znode节点,分别对应:

    • PERSISTENT:永久节点
    • EPHEMERAL:临时节点
    • PERSISTENT_SEQUENTIAL:永久节点 序列化
    • EPHEMERAL_SEQUENTIAL:临时节点序列化

    我们可以通过ZooKeeper系列号临时节点来实现分布式锁。

    每个Znode都包含一系列的属性,下面是一些常见的节点的属性:

    • dataVersion:数据版本号,每次对节点进行set操作,dataVersion的值都会增加1.
    • cversion:子节点的版本号。当znode的子节点有变化时,cversion的值就会增加1
    • aclVersion:ACL版本号,用于访问权限控制
    • cZxid:Znode创建的事务id
    • mZxid:Znode被修改的事务id,即每次对znode的修改都会更新mZxid
    • ephemeralOwner:如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id,如果不是,ephemeralOwner值为0

    2. 读写流程

    下图展示了ZooKeeper的读写流程:


    ZooKeeper读写流程.PNG

    对于读请求,不论client连接的是哪一种角色,ZooKeeper集群都能直接返回(因为每台机器都保存着相同的数据副本)。

    消息广播

    而对于写请求,如果client请求的是Follower或Observer,那么写请求将被转发到Leader。下面是Leader处理写请求的流程:

    1. Leader 接收到消息请求后,将消息赋予一个全局唯一的 64 位自增 id,叫做:zxid,通过 zxid 的大小比较即可实现因果有序这一特性。
    2. Leader 通过先进先出队列(会给每个follower都创建一个队列,保证发送的顺序性,通过 TCP 协议来实现,以此实现了全局有序这一特性)将带有 zxid 的消息作为一个提案(proposal)分发给所有 follower。
    3. 当 follower 接收到 proposal,先将 proposal 写到本地事务日志,写事务成功后再向 leader 回一个 ACK。
    4. 当 leader 接收到过半Follower的ACK后,leader 就向所有 follower 发送 COMMIT 命令,并在本地执行事务提交。
    5. 当 follower 收到消息的 COMMIT 命令时,就会提交事务从而写操作生效。

    上面的流程也被称为消息广播。

    ZAB协议(ZooKeeper使用的分布式一致性协议)的消息广播过程使用的是一个原子广播协议,类似于一个2PC提交过程,针对每个客户端的事务请求,leader服务器会为其生成对应的事务Proposal,并将其发送给集群中其余所有的机器,然后再分别收集各自的选票,最后进行事务提交。

    上面的消息广播过程在Leader宕机时可能会出现两个中间状态:

    1. 当 leader 收到合法数量 follower 的 ACKs 后,就向各个 follower 广播 COMMIT 命令,同时也会在本地执行 COMMIT 并向连接的客户端返回「成功」。但是如果在各个 follower 在收到 COMMIT 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。

    2. 当 leader生成 proposal后就挂了,其他 follower 并没有收到此 proposal(或者只有一小部分收到了这条proposal),那么这条消息就是执行失败的。如果之前挂了的 leader重新启动并成为了新的leader,他保留了这条proposal,就可能将这条proposal同步给其他Follower并进行提交,那么这条proposal就是一条脏数据了。

    因此,在Leader宕机后,ZooKeeper(ZAB协议)会快速的进行Leader重新选举,即崩溃恢复过程。

    崩溃恢复

    ZAB协议规定的崩溃恢复过程必须完成以下两件事:

    1. 对于所有原Leader已经提交了的proposal,新Leader必须能够广播并提交。
    2. 对于原Leader还未广播或只部分广播成功的proposal,新Leader能够通知原Leader或者已经同步了的Follower删除从而保证集群数据的一致性。

    对于要求1,ZAB协议会选举拥有 proposal最大值(即 zxid 最大)的节点作为新的Leader。此时所有的Follower会将自己的最大zxid发送给Leader,Leader对于每一个Follower,将其落后的commit发送给Follower,Follower提交后达成一致性状态。同时,对于落后的proposal,只有当半数Follower都存在这条proposal时,Leader才会将其同步给Follower,否则Leader会通知所有拥有这条proposal的Follower删除。当Follower服务器将所有尚未同步的事务proposal都从Leader服务器同步过来并成功应用到本地后,Leader服务器就会将该Follower加入到真正可用的Follower列表中。如下图所示:

    Leader同步.jpg

    另外,对于要求2,当Follower存在一条半数机器都不存在的proposal时,Leader会通知其删除。

    同时,Zab协议中一个 zxid 是64位,高 32 是纪元(epoch)编号,每经过一次 leader 选举产生一个新的 leader,新 leader 会将 epoch 号 +1。低 32 位是消息计数器,每接收到一条消息这个值 +1,新 leader 选举后这个值重置为 0。ZAB协议通过epoch编号来区分Leader变化周期,能够有效的避免了不同的Leader错误的使用了相同的ZXID编号提出了不一样的proposal的异常情况。

    崩溃恢复的流程可以概括如下:

    1. 首先集群内机器进行投票选举Leader(每台机器都存在三种状态:LOOKING,LEADING和FOLLOWING)
    2. Leader选举成功后向所有Follower进行proposal同步
    3. 同步完成后,Leader正式成为Leader开始对外提供服务

    3. ZAB协议和RAFT协议的对比

    相同点:

    • 都使用timeout来重新选择leader,都采用心跳检测存活性
    • 采用quorum来确定整个系统的一致性(也就是对某一个值的认可),这个quorum一般实现是集群中半数以上的服务器,zookeeper里还提供了带权重的quorum实现
    • 都由leader来发起写操作

    不同点:

    • zab用的ZXID中的epoch和count的组合来表示选举优先级, 而raft用的是term和index
    • Raft中的每个server在某个term轮次内只能投一次票,哪个candidate先请求投票谁就可能先获得投票,这样就可能造成split vote,即各个candidate都没有收到过半的投票,Raft通过candidate设置不同的超时时间,来快速解决这个问题,使得先超时的candidate(在其他人还未超时时)优先请求来获得过半投票。ZooKeeper中的每个server,在某个electionEpoch轮次内,可以投多次票,只要遇到更大的票就更新,然后分发新的投票给所有人。这种情况下不存在split vote现象,同时有利于选出含有更新更多的日志的server,但是选举时间理论上相对Raft要花费的多。
    • raft协议的心跳是从leader到follower,而zab协议则相反

    Raft协议请参考我的文章:https://www.jianshu.com/p/d5ac9eaeab30

    4. ZooKeeper一致性

    首先强一致性(strong consistency)表示任何时刻,任何用户都能读取到最近一次成功更新的数据。由于ZooKeeper在写数据时超过过半Follower的ACK后就返回,因此ZooKeeper可能读取到旧数据,因此显然不是强一致性。

    ZooKeeper支持顺序一致性,即来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将Znode z的值更新为a,在之后的操作中,它又将z的值更新为b,则没有客户端能够在看到z的值是b之后再看到值a(如果没有其他对z的更新)。

    但是有可能client首先连接已经更新为b的机器,读取出值为b,然后再次连接了更新较慢的机器读出值为a。这样就不满足顺序一致性了。ZooKeeper为了解决这个问题,使用了单一视图的概念。

    单一视图:Zookeeper 会为每个消息打上递增的 zxid(zookeeper transactioin id),客户端会维护一个 lastZxid,存放最后一次读取数据对应的 zxid,当客户端连接时,节点会判断 lastZxid 是不是比自己的 zxid 更大,如果是,说明节点的数据比客户端老,拒绝连接。

    参考文章:

    相关文章

      网友评论

          本文标题:ZooKeeper基础

          本文链接:https://www.haomeiwen.com/subject/wscfpctx.html