准备工作:
接下来创建 ZooKeeper 3.7.0 伪集群:
xxxxxxxxxx
tar zxvf apache-zookeeper-3.7.0-bin.tar.gz
xxxxxxxxxx
for i in `seq 1 4`; do cp -r apache-zookeeper-3.7.0-bin apache-zookeeper-3.7.0-bin-$i ; done
xxxxxxxxxx
for i in `seq 1 4`; do cp apache-zookeeper-3.7.0-bin-$i/conf/zoo_sample.cfg apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg ; done
常用配置:
为所有节点创建数据目录、事务日志目录、myid 文件(每个节点的 myid 都不一样)
xxxxxxxxxx
for i in `seq 1 4` ; do mkdir zkdata-$i; mkdir zkdatalog-$i; echo $i >zkdata-$i/myid; done
xxxxxxxxxx
for i in `seq 1 4`; do sed -i "s/^clientPort=[0-9]\{1,\}/clientPort=218$i/g" apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg && sed -i "s/^dataDir=.*/dataDir=zkdata-$i\//g" apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg && echo "dataLogDir=zkdatalog-$i/" >>apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg && for i_ in `seq 1 4`; do echo "server.$i_=0.0.0.0:188$i_:288$i_" >>apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg ; done ; done
xxxxxxxxxx
for i in `seq 1 4`; do sed -i "s/^server.4=\(.*\):\([0-9]\{1,\}\):\([0-9]\{1,\}\)$/server.4=\1:\2:\3:observer/g" apache-zookeeper-3.7.0-bin-$i/conf/zoo.cfg ; done
xxxxxxxxxx
for i in `seq 1 4`; do apache-zookeeper-3.7.0-bin-$i/bin/zkServer.sh start ; done
xxxxxxxxxx
for i in `seq 1 4`; do apache-zookeeper-3.7.0-bin-$i/bin/zkServer.sh status ; done
ZooKeeper 在恢复模式下,选举 Leader 使用的是 Paxos 算法。Paxos 是一种分布式一致性算法,其解决的问题是:分布式系统如何就一个值达成一致。在 Paxos 中,多个节点之间就同一个值达成一致的过程叫 Paxos Instance。
在 Basic Paxos 算法中,主要有两类角色:
Proposer:
Acceptor:
负责批准提案。其内部维护三个值:
如上图所示,Paxos 分为两个阶段:
Prepare 阶段
Proposer 选择新提案编号 n(提案编号递增,且不重复),然后向所有 Acceptor 广播 Prepare(n) 请求
当 Acceptor 收到 Prepare(n) 请求时:
Accept 阶段
等待一段时间后,如果 Proposer 收到大多数 Acceptor 的响应,则会向所有 Acceptor 发送 Accept(N, V)请求,其中 N 的值是 n,V 是所有 Acceptor 返回的 Prepare(n) 响应中最大的 AcceptedProposal 对应的 AcceptedValue,如果所有 Acceptor 都没返回(AcceptedProposal,AcceptedValue),那么 V 由 Proposer 指定
Acceptor 在收到 Accept(N,V)请求时:
等待一段时间后,如果 Proposer 收到大多数 Acceptor 的响应,则达成一致;否则跳转到 Prepare 阶段的第一步
通过上述流程可知,在并发情况下,Paxos 存在活锁问题:
Paxos 的特点是:每个 Proposer 都可以提出 value,但 Proposer 不会“坚持”自己的 value,而是通过 Prepare 阶段,去“学习”其它 Proposer 提出的 value(图 1 中的第 4 步)。
ZooKeeper 集群中的角色主要有以下三类:
角色 | 描述 |
---|---|
Leader | 负责发起提议;更新系统状态 |
Follower | 负责接受客户端请求,向客户端返回结果(在收到写请求时 Forward 给 Leader);选举过程中参与投票 |
Observer | 与 Follower 类似,但在选举过程中不参与投票,只同步 Leader 的状态。Observer 的存在是为了扩展系统,提高读取速度 |
Client | 请求的发起者 |
系统模型如下所示:
ZooKeeper 的特性:
最终一致性:
原子性:
顺序性
ZooKeeper 的数据模型和 Unix 文件系统很像,都是具有层级关系的树形结构。ZooKeeper 中的每个节点叫 ZNode,ZNode 可以用其路径唯一标识(注意:ZNode 只能使用绝对路径表示,不能使用相对路径表示。除根节点外,路径名不能以 “/” 结尾)。每个 ZNode 可以存储少量数据(默认是1M,可配置。注意:因为 ZooKeeper 在运行时会把 ZNode 加载进内存,所以不建议在 ZNode 上存储大量数据。)。另外,ZNode上还存储 ACL 信息,ZNode 的 ACL 和 Unix 文件系统的 ACL 完全不同,每个 ZNode 的 ACL 互相独立,子节点不会继承父节点的 ACL,关于 ZooKeeper 的 ACL,请参考其它文章。
ZNode 的类型:
下面通过示例,看一下 ZNode 的属性:
xxxxxxxxxx
[zk: localhost:2181(CONNECTED) 13] create /test-node test-value
Created /test-node
[zk: localhost:2181(CONNECTED) 14] get -s /test-node
test-value
cZxid = 0x300000004
ctime = Thu Feb 03 16:02:29 CST 2022
mZxid = 0x300000004
mtime = Thu Feb 03 16:02:29 CST 2022
pZxid = 0x300000004
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 0
版本号:
下面以数据版本号为例,说明版本号的作用。假设实现 AtomicInteger,该数据类型提供原子的 incr <number> 操作。那么 ZooKeeper Client 的做操可能是:
get -s /AtomicInteger
:得到数值 value1 和 版本号 version1
set -v <version1> /AtomicInteger <value2>
,其中 value2 = value1 + number:
如果在 set 时,不校验版本号,那么可能导致其它客户端的更新丢失。
事务 ID
时间戳
接下来介绍 ZooKeeper 的 Watch 特性:
ZooKeeper 支持 Watch 操作,Client 可以在 ZNode 上设置 Watcher(比如 get -w /test-node
),当 ZNode 发生相应的变化时,会触发 Watcher,把发生的事件通知给设置 Watcher 的 Client。需要注意的是:Watcher 是一次性的,即触发一次之后,就会被取消,如果想继续 Watch,Client 需要重新设置 Watcher。
使用命令行客户端连接到 ZooKeeper,即可执行命令:
xxxxxxxxxx
/path/to/zookeeper_install_directory/bin/zkCli.sh -server your_host:your_port
ZooKeeper 支持如下命令:
xxxxxxxxxx
addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b|-N|-B] path
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
ls [-s] [-w] [-R] path
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b|-N|-B val path
stat [-w] path
sync path
version
whoami
下面使用一些示例进行说明:
ls /a/znode
ls -w /a/znode
get -s /a/znode
get -s -w /a/znode
set /a/znode data
set -v <version> /a/znode data
create -e /a/znode data
create -s /a/znode data
create /a/znode data
sync /a/znode
1,配置管理
解决多个 Job 实例共享及更新配置的问题
实现方法:
2,Leader 选举
Leader 选举的本质是:多个进程“抢”同一个互斥锁,抢到锁的进程成为 Active 进程,没有抢到锁的进程成为 StandBy 进程
实现方法:
每个进程在启动时,都去 ZooKeeper 集群上注册一个临时顺序节点
获取其父节点的所有子节点列表
判断自己是不是序号最小的子节点:
主线程判断 isLeader 标记,如其为 true,则表示该进程是 Leader,执行一次 real logic;否则,主线程进入阻塞状态,等待被唤醒,当主线程被唤醒时,重新判断 isLeader 标记,如此反复循环......
当创建次小节点的进程退出时,次小节点会被 ZooKeeper 删除,并且触发该进程在其上设置的 Watcher,此时,事件线程需要获取父节点的所有子节点,并判断该临时节点是不是序号最小的子节点
必须考虑连接丢失的情况,当连接丢失时,需要将 isLeader 设置为 false。重连时,需要删除旧的临时顺序节点,创建新的临时顺序节点,然后重新判断新建的临时顺序节点是不是序号最小的节点
3,命名服务
在分布式系统中,通过使用命名服务,客户端能够通过指定的名字来自动发现资源或服务的地址、提供方等信息
实现方法:
4,屏障(Barrier)
5,双屏障
6,组员管理
在 Master-Slave 结构的分布式系统中,Master 需要作为“总管”,来管理所有 Slave,当有 Slave 加入,或有 Slave 宕机时,Master 需要感知到,然后作出对应的调整,以便不影响整个集群对外提供服务
实现方法:
7,分布式锁
下面是博主实现的读写锁(互斥锁的实现就是 LeaderElection 的实现)
读锁是共享的,写锁是独占的;当读锁存在时,其它进程的读锁可立即获取到,但是写锁需要等待;当写锁存在时,其它进程的读锁和写锁都需要等待
实现方法:
两阶段提交用来保证分布式事务的原子性,两阶段提交中的角色分为:协调者(1 个)和参与者(多个),其过程如下:
协调者记录 Prepare T 日志,并向所有参与者发送 Prepare T 消息
参与者收到 Prepare T 消息后,在自身执行事务的预处理,此时存在两种情况:
协调者收集所有参与者返回的意见,此时存在三种情况:
ZooKeeper Atomic Broadcast(简称 ZAB,Zookeeper 原子消息广播协议)是 ZooKeeper 保证数据一致性的核心算法。它分为两种模式:
恢复模式:
ZooKeeper 集群进入恢复模式的情况如下:
ZooKeeper集群进入恢复模式后:
消息广播模式:
ZAB 的消息广播过程类似于两阶段提交,但是移除了 Abort 逻辑,Follower 要么 ACK Leader 发来的 Proposal,要么不认该 Leader
ZAB 的崩溃恢复需要保证以下两种情况:
Server1 是 Leader,C2 在 Leader 上完成事务提交,但在通知 Follower 服务器 Commit 时挂掉,保证 C2 在 Server2 和 Server3 上提交
假设初始的 Leader Server1 在提出事务 Proposal3(P3)后,还没有给 Follower 发送请求时宕机,那么丢弃 P3
ZAB 选举算法要求: