概述
Zookeeper字面上理解就是动物管理员,是大数据框架Hadoop生态圈中的一个服务中间件,Hadoop生态圈中很多开源项目使用动物命名,那么需要一个管理员来管理这些“动物”。他负责分布式应用程序协调的工作。
Hadoop框架
Zookeeper主要提供以下四点功能:统一命名服务、配置管理、集群管理、共享锁和队列管理
,用于高效的管理集群的运行。
Zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。
Zookeeper 部署有三种方式,单机模式、集群模式、伪集群模式,以下采用Docker 的方式部署
注意: 集群为大于等于3个奇数,如 3、5、7,不宜太多,集群机器多了选举和数据同步耗时长,不稳定。
zk安装
docker-compose-yml
version: '3.1'
services:
zoo1:
image: zookeeper
restart: always
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888
配置说明
- 2181:客户端连接 Zookeeper 集群使用的监听端口号
- 3888:选举 leader 使用
- 2888:集群内机器通讯使用(Leader 和 Follower 之间数据同步使用的端口号,Leader 监听此端口)
验证是否安装成功
- 以交互的方式进入容器
docker exec -it zookeeper_zoo1_1 /bin/bash
- 使用客户端连接到服务端
bash-4.3# ./bin/zkCli.sh
Connecting to localhost:2181
- 使用服务端工具检查服务器状态
bash-4.3# ./bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Mode: standalone
ZK集群安装
略
可以参考这个网页
https://www.cnblogs.com/kingkoo/p/8732448.html
ZK常用命令
命令 | 作用 | 示例 |
---|---|---|
help | 查看zk命令清单 | help |
ls | 列出文件及目录 | ls / |
create | 创建节点 | create /test "bb"、create -e /test/e1 "bb" |
get | 获取节点信息 | get /test/e1 |
set | 修改节点内容 | set /aa haha123 |
delete | 删除节点 | delete /test |
通过上面的命令操作,我们不难看出ZK是通过一个特殊的文件系统,帮我们系统分布式系统间的协调。
- 原则性:根据文件系统的原子性,同一个目录下不能创建2个相同的文件
- 独特性:可以创建跟客户端绑定的临时文件
服务器命令
./zkServer.sh start //启动服务
./zkServer.sh stop //停止服务
./zkServer.sh restart //重启服务器
./zkServer.sh //执行状态
注:Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M
什么是分布式锁
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
比如:12306抢票,10W人抢同一张火车票。这个时候用户并发数已经超过了mysql所能承受的极限,无法再用数据库实现分布式锁,必须通过zk等来实现。
分布式锁的实现有哪些
- Memcached:利用 Memcached 的
add
命令。此命令是原子性操作,只有在key
不存在的情况下,才能add
成功,也就意味着线程得到了锁。 - Redis:和 Memcached 的方式类似,利用 Redis 的
setnx
命令。此命令同样是原子性操作,只有在key
不存在的情况下,才能set
成功。 - Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。
- Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。
Zookeeper 的数据模型
Zookeeper 的数据模型是什么样子呢?它很像数据结构当中的树,也很像文件系统的目录。
Zookeeper 的数据模型
树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,这种节点叫做 Znode
但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:
/动物/猫
/汽车/宝马
这样的层级结构,让每一个 Znode 节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。
Znode 包含哪些元素
Znode元素
- data:Znode 存储的数据信息。
- ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点。
- stat:包含 Znode 的各种元数据,比如事务 ID、版本号、时间戳、大小等等。
- child:当前节点的子节点引用
这里需要注意一点,Zookeeper 是为读多写少的场景所设计。Znode 并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点的数据最大不能超过 1MB
。
Zookeeper 的事件通知
Zookeeper 客户端在请求读操作的时候,可以选择是否设置 Watch。我们可以把 Watch 理解成是注册在特定 Znode 上的触发器。当这个 Znode 发生改变,也就是调用了 create
,delete
,setData
方法的时候,将会触发 Znode 上注册的对应事件,请求 Watch 的客户端会接收到异步通知。
具体交互过程如下:
- 客户端调用
getData
方法,watch
参数是true
。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被 Watch 的 Znode 路径,以及 Watcher 列表。
image
- 当被 Watch 的 Znode 已删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。
image
Zookeeper 的一致性
Zookeeper 身为分布式系统协调服务,如果自身挂了如何处理呢?为了防止单机挂掉的情况,Zookeeper 维护了一个集群。如下图:
image
Zookeeper Service 集群是一主多从结构。
在更新数据时,首先更新到主节点(这里的节点是指服务器,不是 Znode),再同步到从节点。
在读取数据时,直接读取任意从节点。
为了保证主从节点的数据一致性,Zookeeper 采用了 ZAB 协议,这种协议非常类似于一致性算法 Paxos和 Raft。
什么是 ZAB
Zookeeper Atomic Broadcast,有效解决了 Zookeeper 集群崩溃恢复,以及主从同步数据的问题。
ZAB 协议定义的三种节点状态
- Looking :选举状态。
- Following :Follower 节点(从节点)所处的状态。
- Leading :Leader 节点(主节点)所处状态。
最大 ZXID
最大 ZXID 也就是节点本地的最新事务编号,包含 epoch 和计数两部分。epoch 是纪元的意思,相当于 Raft 算法选主时候的 term。
ZAB 的崩溃恢复
假如 Zookeeper 当前的主节点挂掉了,集群会进行崩溃恢复。ZAB 的崩溃恢复分成三个阶段:
Leader election
选举阶段,此时集群中的节点处于 Looking 状态。它们会各自向其他节点发起投票,投票当中包含自己的服务器 ID 和最新事务 ID(ZXID)。
image
接下来,节点会用自身的 ZXID 和从其他节点接收到的 ZXID 做比较,如果发现别人家的 ZXID 比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的 ZXID 所属节点。
image
每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准 Leader,状态变为 Leading。其他节点的状态变为 Following。
image
Discovery
发现阶段,用于在从节点中发现最新的 ZXID 和事务日志。或许有人会问:既然 Leader 被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?
这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个 Leader 的情况。
所以这一阶段,Leader 集思广益,接收所有 Follower 发来各自的最新 epoch 值。Leader 从中选出最大的 epoch,基于此值加 1,生成新的 epoch 分发给各个 Follower。
各个 Follower 收到全新的 epoch 后,返回 ACK 给 Leader,带上各自最大的 ZXID 和历史事务日志。Leader 选出最大的 ZXID,并更新自身历史日志。
Synchronization
同步阶段,把 Leader 刚才收集得到的最新历史事务日志,同步给集群中所有的 Follower。只有当半数 Follower 同步成功,这个准 Leader 才能成为正式的 Leader。
自此,故障恢复正式完成。
选举原理总结
-
- 每个 server 发出一个投票: 投票的最基本元素是(SID-服务器id,ZXID-事物id)
-
- 接受来自各个服务器的投票
处理投票:优先检查 ZXID(数据越新ZXID越大),ZXID比较大的作为leader,ZXID一样的情况下比较SID
- 接受来自各个服务器的投票
-
- 统计投票:这里有个过半的概念,大于集群机器数量的一半,即大于或等于(n/2+1),我们这里的由三台,大于等于2即为达到“过半”的要求。这里也有引申到为什么 Zookeeper 集群推荐是单数。
Zookeeper同步流程
选完Leader以后,zk就进入状态同步过程。
1、Leader等待server连接;
2、Follower连接leader,将最大的zxid发送给leader;
3、Leader根据follower的zxid确定同步点;
4、完成同步后通知follower 已经成为uptodate状态;
5、Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
Java三种ZooKeeper客户端比较
原生 | zkClient | Curator |
---|---|---|
直接使用Zookeeper原生API的人并不多,因为: 1)连接的创建是异步的,需要开发人员自行编码实现等待 2)连接没有超时自动的重连机制 3)Zookeeper本身没提供序列化机制,需要开发人员自行指定,从而实现数据的序列化和反序列化 4)Watcher注册一次只会生效一次,需要不断的重复注册 5)Watcher的使用方式不符合java本身的术语,如果采用监听器方式,更容易理解 6)不支持递归创建树形节点 | ZkClient是Github上的一个开源Zookeeper客户端,是由Datameer工程师Stefan Groschupf和Peter Voss一起开发。ZkClient在原生Zookeeper api的基础上进行封装,是一个更易用的客户端,解决和如下问题: 1)session会话超时重连 2)解决Watcher反复注册 3)简化API开发 | Curator是Netflix公司开源的一套Zookeeper客户端框架,作者是Jordan Zimmerman。Curator解决除了ZkClient提供的功能外,新增如下功能: 1)提供了一套Fluent风格的客户端API框架。 2)提供了各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计数器)的抽样封装。 |