介绍
- 开源的分布式协调框架
- 集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性
- 使用改编的两阶段提交协议来保证server节点的事务一致性
官网:https://zookeeper.apache.org
应用场景
- 数据发布/订阅
- 负载均衡
- 命名服务
- 分布式协调/通知
- 集群管理
- Master选举
- 分布式锁
- 分布式队列
特点
特点 | 说明 |
---|---|
顺序一致性 | 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 |
原子性 | 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 |
单一系统映像 | 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 |
可靠性 | 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 |
基本概念
两阶段提交
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
ZKID
类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID。为了保证顺序性,该zkid必须单调递增。
因此ZooKeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。
低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zkid的全局递增性。
历史队列
每一个follower节点都会有一个先进先出(FIFO)的队列用来存放收到的事务请求,保证执行事务的顺序。
可靠提交由ZAB的事务一致性协议保证 全局有序由TCP协议保证 因果有序由follower的历史队列(history queue)保证。
存活条件
只要半数以上节点存活,ZooKeeper 就能正常服务。
存储方式
ZooKeeper 将数据保存在内存中,这也就保证了高吞吐量和低延迟。
持久节点
一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。
临时节点
它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。
SEQUENTIAL
ZooKeeper允许用户为每个节点添加一个特殊的属性:SEQUENTIAL。
一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。
Watcher
Watcher(事件监听器),是Zookeeper中的一个很重要的特性。
Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。
ACL
Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
Zookeeper 定义了如下5种权限:
权限 | 说明 |
---|---|
CREATE | 创建子节点的权限 |
READ | 获取节点数据和子节点列表的权限 |
WRITE | 更新节点数据的权限 |
DELETE | 删除子节点的权限 |
ADMIN | 设置节点ACL的权限 |
ZAB协议
ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。
基于该协议,ZooKeeper 实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB协议运行过程中,所有的客户端更新都发往Leader,Leader写入本地日志后再复制到所有的Follower节点。
一旦Leader节点故障无法工作,ZAB协议能够自动从Follower节点中重新选择出一个合适的替代者,这个过程被称为选主,选主也是ZAB协议中最为重要和复杂的过程。本文主要描述ZAB协议的选主过程以及在Zookeeper中的实现。
服务器状态
服务器状态 | 说明 |
---|---|
LOOKING | 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。 |
FOLLOWING | 跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。 |
LEADING | 领导者状态。表明当前服务器角色是Leader,它会维护与Follower间的心跳。 |
OBSERVING | 观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。 |
集群角色
标题 | 说明 |
---|---|
领导者(Leader) | 领导者负责进行投票和决议,更新系统状态 |
跟随者(Follower) | 用于接收客户请求并向客户端返回结果,在选主过程中参与投票 |
观察者(ObServer) | 可以接收客户端连接,将写请求转发给leader节点。ObServer不参加投票过程,只同步leader的状态。 ObServer的目的是为了扩展系统,提高读取速度。 |
Quorum
集群中超过半数的节点集合。
选票数据结构
每个服务器在进行领导选举时,会发送如下关键信息:
序号 | 字段 | 说明 |
---|---|---|
1 | logicClock | 每个服务器会维护一个自增的整数,名为logicClock,它表示这是该服务器发起的第多少轮投票 |
2 | state | 当前服务器的状态 |
3 | self_id | 当前服务器的myid |
4 | self_zxid | 当前服务器上所保存的数据的最大zxid |
5 | vote_id | 被推举的服务器的myid |
6 | vote_zxid | 被推举的服务器上所保存的数据的最大zxid |
ZAB协议阶段
ZAB协议定义了:选举(Election)、发现(Discovery)、同步(Sync)、广播(Broadcast)4个阶段。
ZAB选举时,当Follower存在ZXID(事务ID)时,判断所有Follower节点的事务日志,只有lastZXID的节点才有资格成为Leader。这种情况下选举出来的Leader总有最新的事务日志,基于这个原因ZK实现时,把发现(Discovery)和同步(Sync)合并为恢复(Recovery)阶段。
ZAB协议选举流程
每个Follower都向其他节点发送自投票(Vote)的请求,等待回复。
Follower接收到的Vote如果比自身的大(ZXID更大),则投票并更新自身的Vote,否则拒绝投票。
每个Follower中都维护着一个投票记录表,当某个节点(发送方)接收到过半投票时,投票结束并把该Follower选为Leader。
运维
单服务器安装
CentOS7
安装jdk
略
安装Zookeeper
# 下载Zookeeper
$ cd /usr/local
$ wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.5/apache-zookeeper-3.5.5-bin.tar.gz
# 安装Zookeeper
$ tar zxvf apache-zookeeper-3.5.5-bin.tar.gz
$ ln -s apache-zookeeper-3.5.5-bin zookeeper
$ cd zookeeper/conf/
$ cp cp zoo_sample.cfg zoo.cfg
$ vi zoo.cfg
##### zoo.cfg内容开始 #####
# Zookeeper最小时间单元,单位毫秒(ms)
ticketTime=2000
# Leader服务器等待Follower启动并完成数据同步的时间,默认值10,表示tickTime的10倍
initLimit=10
# Leader服务器和Follower之间进行心跳检测的最大延时时间,默认值5,表示tickTime的5倍
syncLimit=2
# 服务器对外服务端口,一般设置为2181
clientPort=2181
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/logs
##### zoo.cfg内容结束 #####
# 配置环境变量
$ vi /etc/profile
##### 在末端加入如下内容 开始 #####
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
##### 在末端加入如下内容 结束 #####
$ source /etc/profile
# 启动Zookeeper
$ zkServer.sh start
# 检查zk状态(方法1)
$ zkServer.sh status
# 检查zk状态(方法2)
$ jps
3656 QuorumPeerMain # 显示这个进程说明启动成功
3864 Jps
# 检查zk状态(方法3,寻找2181端口)
$ netstat -ntlp
集群安装Zookeeper
环境说明
主机名 | IP | 操作系统 | 角色 |
---|---|---|---|
node1 | 192.168.100.101 | CentOS7 | ZK |
node2 | 192.168.100.102 | CentOS7 | ZK |
node3 | 192.168.100.103 | CentOS7 | ZK |
安装ZooKeeper集群(node1、node2、node3)
# 安装jdk
$ yum localinstall jdk-8u211-linux-x64.rpm
$ java -version
# 下载Zookeeper
$ cd /usr/local
$ wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.5/apache-zookeeper-3.5.5-bin.tar.gz
# 安装Zookeeper
$ tar zxvf apache-zookeeper-3.5.5-bin.tar.gz
$ ln -s apache-zookeeper-3.5.5-bin zookeeper
# 创建数据目录
$ mkdir -p zookeeper/data/
# 创建myid文件
$ touch zookeeper/data/myid
$ echo "1" >> zookeeper/data/myid # 在node1上执行
$ echo "2" >> zookeeper/data/myid # 在node2上执行
$ echo "3" >> zookeeper/data/myid # 在node3上执行
$ cd zookeeper/conf/
$ cp cp zoo_sample.cfg zoo.cfg
$ vi zoo.cfg
##### zoo.cfg内容开始 #####
ticketTime=2000
initLimit=10
syncLimit=5
clientPort=2181
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/logs
server.1=192.168.100.101:2888:3888
server.2=192.168.100.102:2888:3888
server.3=192.168.100.103:2888:3888
##### zoo.cfg内容结束 #####
# 配置环境变量
$ vi /etc/profile
##### 在末端加入如下内容 开始 #####
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
##### 在末端加入如下内容 结束 #####
$ source /etc/profile
$ zkServer.sh start
# 检查zk状态(方法1)
$ zkServer.sh status
# 检查zk状态(方法2)
$ jps
3656 QuorumPeerMain # 显示这个进程说明启动成功
3864 Jps
# 检查zk状态(方法3,寻找2181端口)
$ netstat -ntlp
管理Zookeeper
命令 | 说明 |
---|---|
zkServer.sh start | 启动 |
zkServer.sh stop | 停止 |
zkServer.sh restart | 重启 |
zkServer-initialize.sh | 初始化数据,需要将要初始化的数据存放在/conf/ 目录中。 |
zkCli.sh | 使用客户端连接 ZK |