尚硅谷-Zookeeper3.5 视频记录笔记

Zookeeper负责存储和管理大家关心的数据,然后接受观察者注册,一旦这些数据发生了变化,Zookeeper就会将通知已经在Zookeeper上注册的观察者。
zookeepeer的用途:服务发现、分布式锁、分布式领导选举、配置管理等。
Zookeeper=文件系统+通知机制

Zookeeper的特点:
1)一个领导者,多个跟随者组成的集群
2)集群中只要有半数以上的节点存活,Zookeeper集群就能正常运行。所以Zookeeper适合安装奇数台服务器,偶数台服务器将会浪费一台服务器(假设有6台服务器,他的半数是3,半数以上为最少为4,只有查过了3台存活才会有正常运行,允许2台服务器挂掉;如果是5台服务器,它的半数是2.5,超过半数也是3,允许2台服务器挂掉。所以说偶数台服务器将会浪费一台服务器,奇数台服务器也能达到同样的效果)。
3)全局数据一致性:每个Server中保存着相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
4)更新请求顺序执行,来自于同一个Client的更新请求按其发送顺序依次执行
5)数据更新的原子性,一次数据更新要么成功要么失败
6)实时性,在一定的范围内,Client能够读到最新的数据

提供的服务:

Zookeeper的数据结构类似于Linux文件系统,每个ZNode默认能够存储1MB数据。
提供的服务:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

  • 统一命令服务:对应用和服务统一命名,便于识别。
  • 统一配置管理

1)分布式环境下:要求所有节点的配置都是一致的,对配置文件修改后能够快速同步到各个节点上;
2)配置管理交由Zookeeper进行管理:可以将配置信息写入到Zookeeper上的一个ZNode,各个客户端监听这个ZNode。一旦发生修改,Zookeeper将会同步到所有的节点上。

  • 统一集群管理

1)实时掌握节点的状态
2)实时监控节点的变化,将节点信息写入Zookeeper上的一个ZNode,可以监听这个ZNode获取它的实时状态。

  • 服务器动态上下线:客户端能够实时洞察服务器的上下线变化。
  • 软负载均衡:Zookeeper中记录每台服务器的访问数,让访问最少的服务器去处理最新的客户端请求。

    Zookeeper的参数

    主要是配置conf/zoo.cfg

    1. # The number of milliseconds of each tick
    2. tickTime=2000
    3. # The number of ticks that the initial
    4. # synchronization phase can take
    5. initLimit=10
    6. # The number of ticks that can pass between
    7. # sending a request and getting an acknowledgement
    8. syncLimit=5
    9. # the directory where the snapshot is stored.
    10. # do not use /tmp for storage, /tmp here is just
    11. # example sakes.
    12. dataDir=../data
    13. # the port at which the clients will connect
    14. clientPort=2181
    15. # the maximum number of client connections.
    16. # increase this if you need to handle more clients
    17. #maxClientCnxns=60
  • tickTime=2000:通信心跳的时间,Zookeeper服务器端与客户端的心跳时间,单位为毫秒。

  • initLimit=10:Zookeeper中的LF(leader and follower)的初始通信时限
  • syncLimit=5:LF的同步时限
  • dataDir=../data:保存Zookeeper的数据
  • clientPort:客户端连接的接口,通常是不做修改的

集群上的文件配置

除了上述的配置之外,还需要以下的配置内容:
1) 配置机器的文件标识,在dataDir指定的目录下,新建myid文件,在这里输入自己的身份表示,例如: 2

2) 配置zoo.cfg

  1. server.A=B:C:D

A:是一个数字,表示的是第几个服务器,也就是在myid文件中配置的数字
B:就是这个服务器的地址
C:是这个服务器Follower和集群中leader服务器交换信息的接口
D:如果是万一集群中的服务器挂掉了,需要使用一个端口来重新进行选举,选出一个新的leader

例如:

  1. server.1=server1:2888:3888
  2. server.2=server2:2888:3888
  3. server.3=server3:2888:3888

Zookeeper的选举机制

假设有5台服务器:所以总票数为5.
1)服务器1刚启动时,发生第一次选举,服务器1投自己一票。此时服务器1票,没有达到总票数的一半以上,选举无法完成,服务器1状态保持为LOOKING。
2)服务器2启动,再次发生一次选举。服务器1和2都有自己的一票,此时服务器1发现服务器2的myid自己目前投票选举的服务器大,改为推选服务器2,此时票数为服务器1:0, 服务器2:2。但是服务器的票数没有半数以上的结果,选举无法完成,服务器1,2保持LOOKING。
3)服务器3启动后,发起一次选举。此时因为没有选择出leader,所以服务器1和2把自己的选票投给服务器3。所以服务器3中有3个选票,超过半数以上,服务器3当选为Leader。服务器1和2更改状态为FOLLWING,服务器3更改状态为LEADING。
4)服务器4、5启动之后,因为此时服务器1、2、3并不是LOOKING状态,所以不会更改选票的内容,少数服从多数,服务器4、5将选票投给了3,并更改自己的状态为FOLLOWING、

Zookeeper的每次读写都有事务id(zxid)。

  • SID:服务器ID,用来标识服务器,与myid一致
  • Epoch:每个leader任期的代号,没有leader时同一投票过程中的逻辑时钟值是相同的,每次投票完成之后这个数据就会增加。

zookeeper的投票选举:

第一次启动的投票选举:

image.png

image.png
选举规则:任期id>事务id>服务器id

客戶端命令行:
image.png

查看当前节点详细数据: ls -s /
(1) czxid: 创建节点的事务 zxid每次修改 ZooKeeper 状态都会产生一个 ZooKeeper 事务 ID。事务 ID 是 ZooKeeper 中所
有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之
前发生。
(2) ctime: znode 被创建的毫秒数(从 1970 年开始)
(3) mzxid: znode 最后更新的事务 zxid
(4) mtime: znode 最后修改的毫秒数(从 1970 年开始)
(5) pZxid: znode 最后更新的子节点 zxid
(6) cversion: znode 子节点变化号, znode 子节点修改次数
(7) dataversion: znode 数据变化号
(8) aclVersion: znode 访问控制列表的变化号
(9) ephemeralOwner: 如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0。
(10) dataLength: znode 的数据长度
(11) numChildren: znode 子节点数量

节点的类型:持久节点(目录)、短暂节点(目录)、顺序编号(创建ZNode时设置顺序标识,ZNode节点名称后面会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。)注意:在分布式系统中, 顺序号可以被用于为所有的事件进行全局排序, 这样客户端可以通过顺序号推断事件的顺序 。

image.png

注册一次监听事件,只能生效一次

服务器的动态上下线

服务器的注册对于Zookeeper来讲就是创建节点的过程。

  1. 服务器启动去注册,创建的是临时节点。
  2. 客户端获取到当前的服务器列表,并且注册监听
  3. 如果有服务器下线,则客户端注册的监听事件将会得到通知
  4. 客户端得到服务器节点下线之后,会再次注册监听

Paxos算法

要解决的问题:

image.png

算法描述:

image.png
image.png
image.png

image.png

ZAB协议

消息广播

image.png
(1) 客户端发起一个写操作请求。
(2) Leader服务器将客户端的请求转化为事务Proposal 提案, 同时为每个Proposal 分配一个全局的ID, 即zxid。
(3) Leader服务器为每个Follower服务器分配一个单独的队列, 然后将需要广播的 Proposal依次放到队列中去, 并且根据FIFO策略进行消息发送。
(4) Follower接收到Proposal后, 会首先将其以事务日志的方式写入本地磁盘中, 写入成功后向Leader反馈一个Ack响应消息。
(5) Leader接收到超过半数以上Follower的Ack响应消息后, 即认为消息发送成功, 可以发送commit消息。
(6) Leader向所有Follower广播commit消息, 同时自身也会完成事务提交。 Follower 接收到commit消息后, 会将上一条事务提交。
(7) Zookeeper采用Zab协议的核心, 就是只要有一台服务器提交了Proposal, 就要确保所有的服务器最终都能正确提交Proposal

异常情况:

一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了与过半 Follower的联系,那么就会进入崩溃恢复模式
1) 假设两种服务器异常情况:
(1) 假设一个事务在Leader提出之后, Leader挂了。
( 2) 一个事务在Leader上提交了, 并且过半的Follower都响应Ack了, 但是Leader在Commit消息发出之前挂了。
2) Zab协议崩溃恢复要求满足以下两个要求:
(1) 确保已经被Leader提交的提案Proposal, 必须最终被所有的Follower服务器提交。 (已经产生的提案, Follower必须执行)
(2) 确保丢弃已经被Leader提出的, 但是没有被提交的Proposal。 (丢弃胎死腹中的提案)

Leader选举: 根据上述要求, Zab协议需要保证选举出来的Leader需要满足以下条件:
(1) 新选举出来的Leader不能包含未提交的Proposal。 即新Leader必须都是已经提交了Proposal的Follower服务器节点
(2) 新选举的Leader节点中含有最大的zxid。 这样做的好处是可以避免Leader服务器检查Proposal的提交和丢弃工作。

Zab如何数据同步:
(1) 完成Leader选举后, 在正式开始工作之前(接收事务请求, 然后提出新的Proposal) , Leader服务器会首先确认事务日志中的所有的Proposal 是否已经被集群中过半的服务器Commit。
(2) Leader服务器需要确保所有的Follower服务器能够接收到每一条事务的Proposal, 并且能将所有已经提交的事务Proposal应用到内存数据中。 等到Follower将所有尚未同步的事务Proposal都从Leader服务器上同步过, 并且应用到内存数据中以后,Leader才会把该Follower加入到真正可用的Follower列表中

Zookeeper的源码解析:

image.png

ZAB 协议和 Paxos 算法

Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法

1. ZAB 协议介绍

ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

2. ZAB 协议两种基本的模式:崩溃恢复和消息广播

ZAB 协议包括两种基本的模式,分别是

  • 崩溃恢复 :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致
  • 消息广播当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

    ZAB协议详解

    根据ZAB协议,所有的写操作都必须通过Leader完成,Leader写入本地日志后再复制到所有的Follower节点。

一旦Leader节点无法工作,ZAB协议能够自动从Follower节点中重新选出一个合适的替代者,即新的Leader,该过程即为领导选举。该领导选举过程,是ZAB协议中最为重要和复杂的过程。

1、写leader

Zookeeper学习笔记 - 图12
leader的写过程:

  1. 客户端向Leader发起写请求
  2. Leader将写请求以Proposal的形式发给所有Follower并等待ACK
  3. Follower收到Leader的Proposal后返回ACK
  4. Leader得到过半数的ACK(Leader对自己默认有一个ACK)后向所有的Follower和Observer发送Commmit
  5. Leader将处理结果返回给客户端

这里要注意:

  • Leader并不需要得到Observer的ACK,即Observer无投票权
  • Leader不需要得到所有Follower的ACK,只要收到过半的ACK即可,同时Leader本身对自己有一个ACK。上图中有4个Follower,只需其中两个返回ACK即可,因为(2+1) / (4+1) > 1/2
  • Observer虽然无投票权,但仍须同步Leader的数据从而在处理读请求时可以返回尽可能新的数据

2、写Follower/Observer

Zookeeper学习笔记 - 图13
从上图可见:

  • Follower/Observer均可接受写请求,但不能直接处理,而需要将写请求转发给Leader处理
  • 除了多了一步请求转发,其它流程与直接写Leader无任何区别

读操作

Zookeeper学习笔记 - 图14
Leader/Follower/Observer都可直接处理读请求,从本地内存中读取数据并返回给客户端即可。由于处理读请求不需要服务器之间的交互,Follower/Observer越多,整体可处理的读请求量越大,也即读性能越好。

支持的领导选举方法:

FastLeaderElection的原理:
1、myid
每个ZooKeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个ZooKeeper集群唯一的ID(整数)。例如,某ZooKeeper集群包含三台服务器,hostname分别为zoo1、zoo2和zoo3,其myid分别为1、2和3,则在配置文件中其ID与hostname必须一一对应,如下所示。在该配置文件中,server.后面的数据即为myid

server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

2、zxid
类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID。为了保证顺序性,该zkid必须单调递增。因此ZooKeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zkid的全局递增性。

3、服务器状态

  • LOOKING 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。
  • FOLLOWING 跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。
  • LEADING 领导者状态。表明当前服务器角色是Leader,它会维护与Follower间的心跳。
  • OBSERVING 观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。

4、选票数据结构
每个服务器在进行领导选举时,会发送如下关键信息:

  • logicClock 每个服务器会维护一个自增的整数,名为logicClock,它表示这是该服务器发起的第多少轮投票
  • state 当前服务器的状态
  • self_id 当前服务器的myid
  • self_zxid 当前服务器上所保存的数据的最大zxid
  • vote_id 被推举的服务器的myid
  • vote_zxid 被推举的服务器上所保存的数据的最大zxid

FastLeaderElection

集群的选举方式

1、集群启动的领导选举方式:

Zookeeper学习笔记 - 图15
流程为:

  1. 首先投票给自己,(1, 1, 0)第一位数代表投出该选票的服务器的logicClock,第二位数代表被推荐的服务器的myid,第三位代表被推荐的服务器的最大的zxid。
  2. 收到外部的投票之后,需要选票pk,并将更新的值广播出去,并将合适的选票存入自己的票箱中。

Zookeeper学习笔记 - 图16

  1. 根据选票确定角色

根据上述选票,三个服务器一致认为此时服务器3应该是Leader。因此服务器1和2都进入FOLLOWING状态,而服务器3进入LEADING状态。之后Leader发起并维护与Follower间的心跳。
Zookeeper学习笔记 - 图17

2、Follower重启动的选举

1、Follower重启投票给自己
Follower重启,或者发生网络分区后找不到Leader,会进入LOOKING状态并发起新的一轮投票。

2、发现已有Leader后为Follower
服务器3收到服务器1的投票后,将自己的状态LEADING以及选票返回给服务器1。服务器2收到服务器1的投票后,将自己的状态FOLLOWING及选票返回给服务器1。此时服务器1知道服务器3是Leader,并且通过服务器2与服务器3的选票可以确定服务器3确实得到了超过半数的选票。因此服务器1进入FOLLOWING状态。

3、Leader的重新选举

1、Follower发起新投票
Zookeeper学习笔记 - 图18
Leader(服务器3)宕机后,Follower(服务器1和2)发现Leader不工作了,因此进入LOOKING状态并发起新的一轮投票,并且都将票投给自己。

2、广播更新选票
服务器1和2根据外部投票确定是否要更新自身的选票。这里有两种情况:

  1. 服务器1和2的zxid相同。例如在服务器3宕机前服务器1与2完全与之同步。此时选票的更新主要取决于myid的大小
  2. 服务器1和2的zxid不同。在旧Leader宕机之前,其所主导的写操作,只需过半服务器确认即可,而不需所有服务器确认。换句话说,服务器1和2可能一个与旧Leader同步(即zxid与之相同)另一个不同步(即zxid比之小)。此时选票的更新主要取决于谁的zxid较大。

Zookeeper的语义保证

  1. 顺序性:客户端发起的更新会按照顺序发送到Zookeeper上
  2. 原子性:更新操作要么成功,要么失败,不会出现中间的状态
  3. 单一系统镜像:一个客户端无论是连接到哪一个服务器上都可以看到完全一致的系统镜像
  4. 可靠性:一个更新操作一旦被接收即不会意外丢失,除非被其他更新操作覆盖
  5. 最终一致性:写操作最终(而非立即)对客户端可见。

watch机制

所有对 ZooKeeper 的读操作,都可附带一个 Watch 。一旦相应的数据有变化,该 Watch 即被触发。

Watch 有如下特点:

  • 主动推送:Watch被触发时,由 ZooKeeper 服务器主动将更新推送给客户端,而不需要客户端轮询。
  • 一次性:数据变化时,Watch 只会被触发一次。如果客户端想得到后续更新的通知,必须要在 Watch 被触发后重新注册一个 Watch。
  • 可见性:如果一个客户端在读请求中附带 Watch,Watch 被触发的同时再次读取数据,客户端在得到 Watch 消息之前肯定不可能看到更新后的数据。换句话说,更新通知先于更新结果。
  • 顺序性:如果多个更新触发了多个 Watch ,那 Watch 被触发的顺序与更新顺序一致。

分布式锁与领导选举的关键点

1、最多一个获取锁 / 成为Leader
对于分布式锁(这里特指排它锁)而言,任意时刻,最多只有一个进程(对于单进程内的锁而言是单线程)可以获得锁。

对于领导选举而言,任意时间,最多只有一个成功当选为Leader。否则即出现脑裂(Split brain)

2、锁重入 / 确认自己是Leader
对于分布式锁,需要保证获得锁的进程在释放锁之前可再次获得锁,即锁的可重入性。
对于领导选举,Leader需要能够确认自己已经获得领导权,即确认自己是Leader。

3、释放锁 / 放弃领导权
锁的获得者应该能够正确释放已经获得的锁,并且当获得锁的进程宕机时,锁应该自动释放,从而使得其它竞争方可以获得该锁,从而避免出现死锁的状态。

领导应该可以主动放弃领导权,并且当领导所在进程宕机时,领导权应该自动释放,从而使得其它参与者可重新竞争领导而避免进入无主状态。

4、感知锁释放 / 领导权的放弃
当获得锁的一方释放锁时,其它对于锁的竞争方需要能够感知到锁的释放,并再次尝试获取锁。
原来的Leader放弃领导权时,其它参与方应该能够感知该事件,并重新发起选举流程。