简介
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协同服务。ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
应用场景
分布式协调服务
● 配置管理(configuration management):如果我们做普通的 Java 应用,一般配置项就是一个本地的配置文件,如果是微服务系统,各个独立服务都要使用集中化的配置管理,这个时候就需要 ZooKeeper。
● DNS服务(负载轮询)。
● 组成员管理(group membership):比如 HBase 就是用此来做集群的组成员管理。
● 统一服务命名(域名)。
● 各种分布式锁。
分布式锁的实现
原理:
1、zookeeper中规定,在同一时刻,不能有多个客户端创建同一个节点,我们可以利用这个特性实现分布式锁。zookeeper临时节点只在session生命周期存在,session一结束会自动销毁。
2、watcher机制,在代表锁资源的节点被删除,即可以触发watcher解除阻塞重新去获取锁,这也是zookeeper分布式锁较其他分布式锁方案的一大优势。
流程:
谁创建成功该节点,谁就持有锁,创建失败的自己进行阻塞,A线程先持有锁,B线程获取失败就会阻塞,同时对/lockPath设置监听,A线程执行完操作后删除节点,触发监听器,B线程此时解除阻塞,重新去获取锁。
java代码实现采用模板方法模式:
1、创建Lock接口
2、AbstractTemplateLock抽象类
3、zookeeper分布式锁逻辑
缺点:
每次去竞争锁,都只会有一个线程拿到锁,当线程数庞大时会发生“惊群”现象,zookeeper节点可能会运行缓慢甚至宕机。这是因为其他线程没获取到锁时都会监听/lockPath节点,当A线程释放完毕,海量的线程都同时停止阻塞,去争抢锁,这种操作十分耗费资源,且性能大打折扣。
与Redis的分布式锁的比较
Redis 实现的分布式锁的话,不能够 100% 保证可用性 ,因为在真实环境中使用分布式锁,一般都会集群部署 Redis ,来避免单点问题,那么 Redisson 去 Redis 集群上锁的话,先将锁信息写入到主节点中,如果锁信息还没来得及同步到从节点中,主节点就宕机了,就会导致这个锁信息丢失(可以加红锁RedLock,牺牲性能)
ZooKeeper 的分布式锁是 基于临时节点 来做的,多个客户端去创建临时同一个节点,第一个创建客户端抢锁成功,释放锁时只需要删除临时节点即可。所以zk的分布式锁适用于对可靠性要求较高的业务场景,这里是相对于 Redis 分布式锁来说相对更见健壮 一些
!!!!!!!!!
ZooKeeper 适用于存储和协同相关的关键数据,不适合用于大数据量存储。如果要存 KV 或者大量的业务数据,还是要用数据库或者其他 NoSql (redis)来做。
原因:1、设计方面:ZooKeeper 需要把所有的数据(它的 data tree)加载到内存中。这就决定了ZooKeeper 存储的数据量受内存的限制。
2、工程方面:ZooKeeper 的设计目标是为协同服务提供数据存储,数据的高可用性和性能是最重要的系统指标,处理大数量不是 ZooKeeper 的首要目标。因此,ZooKeeper 不会对大数量存储做太多工程上的优化。
服务使用
要使用 ZooKeeper 服务,首先我们的应用要引入 ZooKeeper 的客户端库,然后我们客户端库和 ZooKeeper 集群来进行网络通信来使用 ZooKeeper 的服务,本质上是 Client-Server 的架构,我们的应用作为一个客户端来调用 ZooKeeper Server 端的服务。
数据模型、命名空间、节点概念
ZooKeeper 的数据模型是层次模型。层次模型常见于文件系统。层次模型和 key-value 模型是两种主流的数据模型。ZooKeeper 使用文件系统模型主要基于以下两点考虑:
- 文件系统的树形结构便于表达数据之间的层次关系。
- 文件系统的树形结构便于为不同的应用分配独立的命名空间(namespace)。
ZooKeeper 的层次模型称作 data tree。Data tree 的每个节点叫做 znode。不同于文件系统,每个节点都可以保存数据。每个节点都有一个版本(version),版本从 0 开始计数。
data tree接口
ZooKeeper 对外提供一个用来访问 data tree的简化文件系统 API:
● 使用 UNIX 风格的路径名来定位 znode,例如 /A/X 表示 znode A 的子节点 X。
● znode 的数据只支持全量写入和读取,没有像通用文件系统那样支持部分写入和读取。
● data tree 的所有 API 都是 wait-free 的,正在执行中的 API 调用不会影响其他 API 的完成。
● data tree 的 API都是对文件系统的 wait-free 操作,不直接提供锁这样的分布式协同机制。但是 data tree 的 API 非常强大,可以用来实现多种分布式协同机制。
znode分类
一个 znode 可以是持久性的,也可以是临时性的,znode 节点也可以是顺序性的。每一个顺序性的 znode 关联一个唯一的单调递增整数,因此 ZooKeeper 主要有以下 4 种 znode: - 持久性的 znode (PERSISTENT): ZooKeeper 宕机,或者 client 宕机,这个 znode 一旦创建就不会丢失。
- 临时性的 znode (EPHEMERAL): ZooKeeper 宕机了,或者 client 在指定的 timeout 时间内没有连接 server,都会被认为丢失。
- 持久顺序性的 znode (PERSISTENT_SEQUENTIAL): znode 除了具备持久性 znode 的特点之外,znode 的名字具备顺序性。
- 临时顺序性的 znode (EPHEMERAL_SEQUENTIAL): znode 除了具备临时性 znode 的特点之外,znode 的名字具备顺序性。
Stat结构体
1、czxid: 创建节点的事务 zxid。
每次修改 ZooKeeper 状态都会收到一个 zxid 形式的时间戳,也就是 ZooKeepe r事务 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 子节点数量。
安装流程
1、下载地址:https://archive.apache.org/dist/zookeeper/stable/
2、把 apache-zookeeper-3.5.6-bin.tar.gz 解压到一个本地目录(不要含空格和中文名字)。
解压:tar -zxvf apache-zookeeper-3.5.6-bin.tar.gz
3、修改conf目录下的zoo_sample.cfg文件,重命名为:zoo.cfg,修改配置
# 心跳检查的时间 2秒
tickTime=2000
# 初始化时 连接到服务器端的间隔次数,总时间10*2=20秒
initLimit=10
# ZK Leader 和follower 之间通讯的次数,总时间5*2=10秒
syncLimit=5
# 存储内存中数据快照的位置,如果不设置参数,更新事务日志将被存储到默认位置。
dataDir=/data/zookeeper
# ZK 服务器端的监听端口
clientPort=2181
4、配置环境变量 vim /etc/profile
export ZOOKEEPER_HOME=/usr/local/apache-zookeeper-3.5.6-bin
export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf
启动zookeeper
● zkServer.sh start /logs 可以查看日志
● /data/zookeeper 可以查看存放的相关文件
● 检查端口:netstat -an | ag 2181
zkCli客户端
命令:conf、cons、dump、reqs、stat、wchs、wchc、whcp等
eg: echo conf | nc 10.127.0.0.1:2181(输出相关服务配置的详情信息)
输入:zkCli.sh命令后,可以与zk建立相关链接(./zkCli.sh -server ip:port)
ls -R / 递归查看所有znode节点
create /node 创建节点
stat nodePath
set nodePath Value
get nodePath
节点监听机制
watch
客户端可以监测znode节点的变化。Zonode节点的变化触发相应的事件,然后清除对该节点的监测。当监测一个znode节点时候,Zookeeper会发送通知给监测节点。在shell终端一个Watch事件是一个一次性的触发器,当被设置了Watch的数据或者目录发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端以便通知它们。
ls -w /appNode (监听节点目录的变化)
get -w nodePath (监听节点数据的变化)
原理:
1、首先要有一个 main() 线程。
2、在 main 线程中创建 Zokeeper 客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener) 。
3、通过 connect 线程将注册的监听事件发送给 Zookeeper。
4、在 Zookeeper 的注册监听器列表中将注册的监听事件添加到列表中。
5、Zookeeper 监听到有数据或路径变化,就会将这个消息发送给 listener 线程。
6、listener 线程内部调用了 process() 方法。
Java操作zk
1、导入依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
2、连接服务端
zkClient = newZkClient("ip:端口号",60000*30,60000,newSerializableSerializer());
3、创建节点
//创建持久节点
zkClient.create("/app2", "2", CreateMode.PERSISTENT);
//创建持久顺序节点
zkClient.create("/app3","3", CreateMode.PERSISTENT_SEQUENTIAL);
//创建临时节点
zkClient.create("/app4","4", CreateMode.EPHEMERAL);
//创建临时顺序节点
zkClient.create("/app5","5",CreateMode.EPHEMERAL_SEQUENTIAL);
4、查看节点信息
Object o = zkClient.readData("/app2");
Stat stat =newStat();
stat.getCtime()、stat.getCversion()、stat.getCzxid()
5、修改节点信息
zkClient.writeData("/app2","xiaoma");
6、查看当前节点的所有子节点
List<String> children = zkClient.getChildren("/");
for(String c : children ){System.out.println(c);}
7、监听
基本概念
角色
设计目的
1.最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
2.可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
3.实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
4.等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
5.原子性:更新只能成功或者失败,没有中间状态。
6.顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
工作原理
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分 别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上 了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个 新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有三种状态:
● LOOKING:当前Server不知道leader是谁,正在搜寻。
● LEADING:当前Server即为选举出来的leader。
● FOLLOWING:leader已经选举出来,当前Server与之同步。
选主流程
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
basic paxos流程:
1.选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server。
2.选举线程首先向所有Server发起一次询问(包括自己)。
3.选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中。
4.收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server。
5.线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1。每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。
fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。
同步流程
选完leader以后,zk就进入状态同步过程。
1.leader等待server连接。
2.Follower连接leader,将最大的zxid发送给leader。
3.Leader根据follower的zxid确定同步点。
4.完成同步后通知follower 已经成为uptodate状态。
5.Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
工作流程(leader)
Leader主要有三个功能:
1.恢复数据。
2.维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型。
3.Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。
PING消息是指Learner的心跳信息;REQUEST消息是Follower发送的提议信息,包括写请求及同步请求;ACK消息是 Follower的对提议的回复,超过半数的Follower通过,则commit该提议;REVALIDATE消息是用来延长SESSION有效时间。(三个线程实现)
工作流程(follower)
Follower主要有四个功能:
1.向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息)。
2.接收Leader消息并进行处理。
3.接收Client的请求,如果为写请求,发送给Leader进行投票。
4.返回Client结果。
Follower的消息循环处理如下几种来自Leader的消息:
1.PING消息: 心跳消息。
2.PROPOSAL消息:Leader发起的提案,要求Follower投票。
3.COMMIT消息:服务器端最新一次提案的信息。
4.UPTODATE消息:表明同步完成。
5.REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息。
6.SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。