ZooKeeper是一个提供高可用,一致性,高性能的保证读写顺序的存储系统。ZAB协议为ZooKeeper专门设计的一种支持数据一致性的原子广播协议。
$ uname -a
Darwin 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64
brew cask install java
brew install zookeeper
这里演示的是在同一台机器部署3个ZooKeeper进程的伪集群。
$ cat /usr/local/etc/zookeeper/zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data1
clientPort=2181
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ echo "1" > /usr/local/var/run/zookeeper/data1/myid
集群中第2个实例的配置为:
$ cat /usr/local/etc/zookeeper/zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data2
clientPort=2182
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ cat /usr/local/var/run/zookeeper/data2/myid
2
集群中第3个示例的配置为:
$ cat /usr/local/etc/zookeeper/zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data3
clientPort=2183
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ cat /usr/local/var/run/zookeeper/data3/myid
3
启动集群:
$ zkServer start /usr/local/etc/zookeeper/zoo1.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo1.cfg
Starting zookeeper ... STARTED
$ zkServer start /usr/local/etc/zookeeper/zoo2.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2.cfg
Starting zookeeper ... STARTED
$ zkServer start /usr/local/etc/zookeeper/zoo3.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo3.cfg
Starting zookeeper ... STARTED
$ zkServer status /usr/local/etc/zookeeper/zoo1.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo1.cfg
Mode: follower
$ zkServer status /usr/local/etc/zookeeper/zoo2.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2.cfg
Mode: leader
$ zkServer status /usr/local/etc/zookeeper/zoo3.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo3.cfg
Mode: follower
从上面的状态检查可以看出,第二实例是leader,其他两个实例是follower。
下面我将演示在集群中读写节点。
$ zkCli -server localhost:2182
Connecting to localhost:2182
Welcome to ZooKeeper!
JLine support is enabled
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2182(CONNECTED) 2] create /test "i am test"
Created /test
[zk: localhost:2182(CONNECTED) 3] get /test
i am test
cZxid = 0x200000002
ctime = Tue Jul 02 16:35:15 CST 2019
mZxid = 0x200000002
mtime = Tue Jul 02 16:35:15 CST 2019
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 0
在实例2中创建/test节点,接下来在实例3中也能读取到该节点,说明ZooKeeper集群数据的一致性。
ZooKeeper简单高效,同时提供以下语义保证,从而使我们可以利用这些特性提供复杂的服务:
所有对ZooKeeper的读操作都可以附带一个Watch。一旦相应的数据有变化,该Watch即被触发。
Watch有以下特点:
为了保证写操作的一致性与可用性,ZooKeeper在paxos的基础上设计了一种名为原子广播(ZAB)的支持崩溃恢复的一致性协议。基于该协议,ZooKeeper实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。
根据ZAB协议,所有的写操作都必须通过leader来完成,leader写入本地日志后再复制到所有的follower节点。如果客户端对follower/observer发起写请求,follower/observer会将请求转发到leader,然后由leader处理完成后再将结果转发回follower/observer发送给客户端。
ZAB协议分为广播模式和崩溃恢复模式
leader处理写请求(广播模式)的步骤为:
1.leader为事务请求生成唯一的事务ID(ZXID),ZAB协议会将每个事务Proposal按照ZXID的先后顺序来进行排序和处理。
2.将Proposal发送给follower并等待follower回复ACK。
3.leader收到超过半数的ACK(leader默认对自己有一个ACK)后向所有的follower/observer发送commit,同时leader自身也会完成commit。
4.将处理结果返回给客户端。
上述过程成为ZooKeeper的两阶段提交。
崩溃恢复:
当leader实例宕机崩溃,或者因为网络原因导致其与过半的follower都失去联系,那么就会进入崩溃恢复阶段。
leader宕机或者与过半的follower失联都会导致leader重新选举(选举算法文章后面会介绍)。选举结束后会紧着进入数据崩溃恢复,以保证数据一致性,也就是数据同步。需要保证已经commit的事务被所有服务器都提交,同时需要丢弃那些只在leader服务器提交的事务。所以选出来的新leader要拥有集群中最高编号的ZXID。在新的leader选举出来后就会进行数据同步工作,leader会将那些没有被follower同步的事务以Proposal消息的形式发送给follower,并在每个Proposal消息后面紧跟着发送一个commit消息,表示该事务已经被提交。然后follower服务器会将同步过来的事务Proposal都成功应用到本地数据库。
ZXID是个64位的无符号整形,高32位是epoch,代表leader选择周期,低32位是累加计数,每一轮选举后该计数会清零。leader服务器没产生一个事务,ZXID的低32位就会加一,没完成一次leader选举,就会将ZXID的高32位加一。这样做是为了保证新leader生成的ZXID肯定是大于旧leader之前产生的ZXID。
服务器状态:
选票数据结构:
每个服务器在进行领导者选举是,会发送如下关键信息:
(快速领导者选举算法)选票PK:
选票PK是基于(logicClock,self_id, self_zxid) 与 (vote_logicClock, vote_self_id, vote_self_zxid)对比: 先比较logicClock,如果相等再比较zxid,如果zxid相等,再比较myid。最后如果vote大于自身,则改票,也投vote。
文章一开始演示ZooKeeper的部署和操作给读者一个直观感受,然后介绍了ZooKeeper的ZAB协议和领导者选举原理。
https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription