zookeeper
一:回顾redis
redis
单实例,但是基于内存,速度快
复制集群(主从复制)—不是绝对的实时同步
HA sentinel
集群模式-分片(一个集群里面看不到所有的数据)
完成分布式协调—使用分布式锁
zookeeper---->分布式协调服务
二:zookeeper概念
https://zookeeper.apache.org/
学习:https://zookeeper.apache.org/doc/current/zookeeperOver.html
zookeeper是一个目录树结构--->node节点,大小1M--->持久节点
临时节点--->session
序列节点(持久节点和临时节点是否是序列节点)
客户端连接到zk会产生session,存储客户端信息
zk特点:
1.统一配置管理,每个节点存1M数据
2.分组管理,通过path结构
3.统一命名管理,通过sequential
4.分布式同步,通过临时节点
zk应用:
1.分布式锁,临时节点-->锁依托一个父节点,且具备-s序列化,父节点下可以有多把锁-->队列式事务的锁 ----->依托client来实现
2.HA,选主节点--->
集群模型也是:主从集群,写只能在leader上,读可以在所有机器上。主节点是单机的。
leader follower follower
1.leader会挂掉,导致服务不可用。但是事实上,zookeeper能实现高可用,zk可以快速恢复一个leader。
zk中有两种状态:可用状态和不可用状态(取决于leader是否挂掉)。zk恢复leader时间小于200ms,因此高可用。
节点概念。节点可以存数据,但是数据量大小限制1M,存小量数据。 因此不能将zk当作数据库使用。
# 获取连接
[root@VM-4-2-centos ~]# netstat -anpt | egrep '(2888|3888)'
tcp6 0 0 :::3888 :::* LISTEN 5099/java
tcp6 0 0 10.0.4.2:3888 124.223.81.248:48328 ESTABLISHED 5099/java
tcp6 0 0 10.0.4.2:47440 124.223.81.248:2888 ESTABLISHED 5099/java
3888端口,选取投票使用
2888端口,leader接收写操作使用
三:zookeeper的读写机制
在ZooKeeper的选举中,如果过半的节点都选一个节点为leader的话,那么这个节点就会是leader节点,也就是因为这个原因,ZooKeeper集群,只要有过半的节点是存活的,那么这个ZooKeeper就可以正常的提供服务。比如有5个ZooKeeper节点,其中有2个节点宕机了,这个时候还有3个节点存活,存活个数超过半数,此时集群还是正常提供服务,所以ZooKeeper集群本身是没有高可用问题的。又因为存活的判断依据是超过半数,所以我们一般搭建ZooKeeper集群的时候,都使用奇数台,这样会比较节约机器,比如我们安装一个6台的ZooKeeper集群,如果宕机了3台就会导致集群不可用,因为这个时候存活的节点数没有超过半数了,所以6台和5台的效果是一样的,我们用5台比较合适。
对应一个ZooKeeper集群,我们可能有多个客户端,客户端能任意连接其中一台ZooKeeper节点,但是所有的客户端都只能往leader节点上面去写数据,所有的客户端能从所有的节点上面读取数据。如果有客户端连接的是follower节点,然后往follower上发送了写数据的请求,这个时候follower就会把这个写请求转发给leader节点处理。leader接受到写请求就会往其他节点(包括自己)同步数据,如果过半的节点接受到消息后发送回来ack消息,那么leader节点就对这条消息进行commit,commit后该消息就对用户可见了。因为需要过半的节点发送ack后,leader才对消息进行commit,这个时候会有一个问题,如果集群越大,那么等待过半节点发送回来ack消息这个过程就需要越久,也就是说节点越多虽然会增加集群的读性能,但是会影响到集群的写性能,所以我们一般建议ZooKeeper的集群规模在3到5个节点左右。为了解决这个问题,后来的ZooKeeper中增加了一个observer 的角色,这个节点不参与投票,只是负责同步数据。比如我们leader写数据需要过半的节点发送ack响应,这个observer节点是不参与过半的数量统计的。它只是负责从leader同步数据,然后提供给客户端读取,所以引入这个角色目的就是为了增加集群读的性能,然后不影响集群的写性能。用户搭建集群的时候可以自己设置该角色。
四:zookeeper的保证
ZooKeeper 非常快速且非常简单。但是,由于它的目标是成为构建更复杂服务(例如同步)的基础,因此它提供了一组保证。这些是:
- 顺序一致性 - 来自客户端的更新将按照它们发送的顺序应用。
- 原子性 - 更新成功或失败。没有部分结果。
- 单一系统映像 - 客户端将看到相同的服务视图,而不管它连接到的服务器如何。即,即使客户端故障转移到具有相同会话的不同服务器,客户端也永远不会看到系统的旧视图。
- 可靠性 - 应用更新后,它将从那时起持续存在,直到客户端覆盖更新。
- 及时性——系统的客户视图保证在一定的时间范围内是最新的。
五:zookeeper的安装和集群
# 安装java1.8
[root@VM-4-2-centos ~]# yum install java-1.8.0-openjdk-devel.x86_64
# 修改zoo_sample.cfg文件
1 # The number of milliseconds of each tick
2 tickTime=2000
3 # The number of ticks that the initial
4 # synchronization phase can take
5 initLimit=10
6 # The number of ticks that can pass between
7 # sending a request and getting an acknowledgement
8 syncLimit=5
9 # the directory where the snapshot is stored.
10 # do not use /tmp for storage, /tmp here is just
11 # example sakes.
12 dataDir=/usr/local/software/zookeeper/var
13 # the port at which the clients will connect
14 clientPort=2181
29 server.1=node01:2888:3888
30 server.2=node02:2888:3888
# 外网连接时要设置0.0.0.0
server.1=118.195.208.143:2888:3888
server.2=0.0.0.0:2888:3888
在/usr/local/software/zookeeper/var中创建文件myid,写入配置的id号:1
# 复制到其他节点,远程拷贝到其他机器
[root@VM-4-2-centos zookeeper]# cp -r ./zookeeper-3.4.6/* ./zookeeper/
[root@VM-4-2-centos zookeeper]# scp -r ./zookeeper root@124.223.81.248:`pwd`
创建并修改远程文件的myid
# 修改系统配置文件
export ZOOKEEPER=/usr/local/software/zookeeper/zookeeper-3.4.6
export PATH=$PATH:$ZOOKEEPER/bin
. /etc/profile
# 在控制台输入zk,并按tab键
[root@VM-4-2-centos var]# zk
zkCleanup.sh zkCli.cmd zkCli.sh zkEnv.cmd zkEnv.sh zkServer.cmd zkServer.sh
其他机器也是同样进行配置
# 启动,查看帮助
[root@VM-4-2-centos var]# zkServer.sh help
JMX enabled by default
Using config: /usr/local/software/zookeeper/zookeeper/bin/../conf/zoo.cfg
Usage: /usr/local/software/zookeeper/zookeeper/bin/zkServer.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}
# 前台启动
[root@VM-16-17-centos ~]# zkServer.sh start-foreground
# 118.195机子
[root@VM-4-2-centos ~]# zkServer.sh status
JMX enabled by default
Using config: /usr/local/software/zookeeper/zookeeper/bin/../conf/zoo.cfg
Mode: follower
# 124.223机子
[root@VM-16-17-centos ~]# zkServer.sh status
JMX enabled by default
Using config: /usr/local/software/zookeeper/bin/../conf/zoo.cfg
Mode: leader
# 启动客户端,默认连接自己
[root@VM-16-17-centos ~]# zkCli.sh
[zk: localhost:2181(CONNECTED) 0]
# 输入help命令
# 测试
[zk: localhost:2181(CONNECTED) 1] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 2] create /ooxx "" # 如果create -e path data代表是创建临时节点
Created /ooxx
[zk: localhost:2181(CONNECTED) 3] ls /
[zookeeper, ooxx]
[zk: localhost:2181(CONNECTED) 4] get /ooxx
""
cZxid = 0x200000002
ctime = Mon Jun 27 13:14:48 CST 2022
mZxid = 0x200000002
mtime = Mon Jun 27 13:14:48 CST 2022
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 2
numChildren = 0
[zk: localhost:2181(CONNECTED) 5] set /ooxx "hello"
cZxid = 0x200000002
ctime = Mon Jun 27 13:14:48 CST 2022
mZxid = 0x200000003
mtime = Mon Jun 27 13:15:49 CST 2022
pZxid = 0x200000002
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
[zk: localhost:2181(CONNECTED) 6] set /ooxx "world"
cZxid = 0x200000002
ctime = Mon Jun 27 13:14:48 CST 2022
mZxid = 0x200000004
mtime = Mon Jun 27 13:16:31 CST 2022
pZxid = 0x200000002
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
[zk: localhost:2181(CONNECTED) 7] get /ooxx
"world"
cZxid = 0x200000002
ctime = Mon Jun 27 13:14:48 CST 2022
mZxid = 0x200000004
mtime = Mon Jun 27 13:16:31 CST 2022
pZxid = 0x200000002
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
# cZxid代表创建的事务id, 后32位:00 00 00 02,4个字节,一个字节8位,32位递增,前面32位:0X2,表示leader次数
# mZxid:修改的事务id
# pZxid:当前节点下,创建最后一个节点的id
# ephemeralOwner= 0x0 临时拥有者,0x0代表没有归属者,节点是持久的
# session 是客户端创建时拥有的,如果客户端断开连接,session则消亡,节点也会消失
# 当有一个客户端连接时,cZxid事务id也会加1,session创建要统一视图。消亡也是一样
# create -s /abc/xx data 加上-s就会进行序列化,这样多个客户端创建同一个节点就不会相互覆盖,而是路径增加 /abc/xx0000000000 /abc/xx0000000001 ...
# 如果删除rmr /abc/xx0000000000 rmr /abc/xx0000000001,那么再次创建时,是从0000000002开始的
六:分布式锁
zk获取分布式锁的大概流程
首先,ZK实现分布式锁是基于临时顺序节点进行实现的。因为可以避免拿到锁的客户端挂掉或者卡死,导致节点无法被删除的问题
# 加锁流程
首先在zookeeper创建根节点,也就是持久节点(rootNode),作为锁节点
在根节点(rootNode)下创建临时节点(node_x):
查询根节点的子节点列表,检查如果当前创建的节点的序号是最小话,就认定该客户端已经获得锁
如果不是最小的节点,说明获取锁失败,此时客户端就要找比自己小1的节点(node_x-1),对其注册事件监听器(等待node_x-1的节点被删除的时候,通知当前节点获取锁)
获取锁的客户端在执行完业务就删除当前最小节点。删除最小节点后客户端会通过监听器收到通知,然后再次判断是否自己的节点最小,是的话进行加锁,不是的话重复上述操作
# 释放锁流程
等待获取锁的客户端执行完业务流程,直接删除当前的最小的临时顺序节点(L)即可
这样监听L的删除事件的客户端,就会被通知到,尝试进行锁的获取
七:可靠性、时序性、扩展性
1.paxos
2.ZAB
3.watch
4.API
5.callable-->reactive响应式编程
7.1 扩展性
zk中有三个角色:leader、follower、observer
zk中是读写分离的,leader负责写,follower负责读。只有follower才能选举,observer负责同步数据,不会参与投票选举
zk配置文件中:
server.1=0.0.0.1:2888:3888
server.2=xxx.xxx.xxx.xxx:2888:3888
server.3=xxx.xxx.xxx.xxx:2888:3888
server.4=xxx.xxx.xxx.xxx:2888:3888.observer
7.2 可靠性
攘其外必先安其内
leader挂掉之后能快速恢复
数据提供最终一致性,最终一致性过程中,节点是否对外提供服务?节点会先向leader进行同步,如果不行,就会停掉服务
paxos算法
paxos算法:基于消息传递的一致性算法。
Paxos有一个前提:没有拜占庭将军问题(网络中节点与节点之间是不可信的)。就是说Paxos只有在一个可信的计算环境中才能成立,这个环境是不会被入侵所破坏的。
paxos算法内容:
先说Paxos,它是一个基于消息传递的一致性算法,Leslie Lamport在1990年提出,近几年被广泛应用于分布式计算中,Google的Chubby,Apache的Zookeeper都是基于它的理论来实现的,Paxos还被认为是到目前为止唯一的分布式一致性算法,其它的算法都是Paxos的改进或简化。有个问题要提一下,Paxos有一个前提:没有拜占庭将军问题。就是说Paxos只有在一个可信的计算环境中才能成立,这个环境是不会被入侵所破坏的。
关于Paxos的具体描述可以在Wiki中找到:zh.wikipedia.org/zh-cn/Paxos… Server的对应关系。
Paxos描述了这样一个场景,有一个叫做Paxos的小岛(Island)上面住了一批居民,岛上面所有的事情由一些特殊的人决定,他们叫做议员(Senator)。议员的总数(Senator Count)是确定的,不能更改。岛上每次环境事务的变更都需要通过一个提议(Proposal),每个提议都有一个编号(PID),这个编号是一直增长的,不能倒退。每个提议都需要超过半数((Senator Count)/2 +1)的议员同意才能生效。每个议员只会同意大于当前编号的提议,包括已生效的和未生效的。如果议员收到小于等于当前编号的提议,他会拒绝,并告知对方:你的提议已经有人提过了。这里的当前编号是每个议员在自己记事本上面记录的编号,他不断更新这个编号。整个议会不能保证所有议员记事本上的编号总是相同的。现在议会有一个目标:保证所有的议员对于提议都能达成一致的看法。
好,现在议会开始运作,所有议员一开始记事本上面记录的编号都是0。有一个议员发了一个提议:将电费设定为1元/度。他首先看了一下记事本,嗯,当前提议编号是0,那么我的这个提议的编号就是1,于是他给所有议员发消息:1号提议,设定电费1元/度。其他议员收到消息以后查了一下记事本,哦,当前提议编号是0,这个提议可接受,于是他记录下这个提议并回复:我接受你的1号提议,同时他在记事本上记录:当前提议编号为1。发起提议的议员收到了超过半数的回复,立即给所有人发通知:1号提议生效!收到的议员会修改他的记事本,将1好提议由记录改成正式的法令,当有人问他电费为多少时,他会查看法令并告诉对方:1元/度。
现在看冲突的解决:假设总共有三个议员S1-S3,S1和S2同时发起了一个提议:1号提议,设定电费。S1想设为1元/度, S2想设为2元/度。结果S3先收到了S1的提议,于是他做了和前面同样的操作。紧接着他又收到了S2的提议,结果他一查记事本,咦,这个提议的编号小于等于我的当前编号1,于是他拒绝了这个提议:对不起,这个提议先前提过了。于是S2的提议被拒绝,S1正式发布了提议: 1号提议生效。S2向S1或者S3打听并更新了1号法令的内容,然后他可以选择继续发起2号提议。
好,我觉得Paxos的精华就这么多内容。现在让我们来对号入座,看看在ZK Server里面Paxos是如何得以贯彻实施的。
ZK Server与ZK Server映射关系:
小岛(Island)——ZK Server Cluster
议员(Senator)——ZK Server
提议(Proposal)——ZNode Change(Create/Delete/SetData…)
提议编号(PID)——Zxid(ZooKeeper Transaction Id)
正式法令——所有ZNode及其数据
貌似关键的概念都能一一对应上,但是等一下,Paxos岛上的议员应该是人人平等的吧,而ZK Server好像有一个Leader的概念。没错,其实Leader的概念也应该属于Paxos范畴的。如果议员人人平等,在某种情况下会由于提议的冲突而产生一个“活锁”(所谓活锁我的理解是大家都没有死,都在动,但是一直解决不了冲突问题)。Paxos的作者Lamport在他的文章”The Part-Time Parliament“中阐述了这个问题并给出了解决方案——在所有议员中设立一个总统,只有总统有权发出提议,如果议员有自己的提议,必须发给总统并由总统来提出。好,我们又多了一个角色:总统。
总统——ZK Server Leader
又一个问题产生了,总统怎么选出来的?oh, my god! It’s a long story.
现在我们假设总统已经选好了,下面看看ZK Server是怎么实施的。
情况一:
屁民甲(Client)到某个议员(ZK Server)那里询问(Get)某条法令的情况(ZNode的数据),议员毫不犹豫的拿出他的记事本(local storage),查阅法令并告诉他结果,同时声明:我的数据不一定是最新的。你想要最新的数据?没问题,等着,等我找总统Sync一下再告诉你。
情况二:
屁民乙(Client)到某个议员(ZK Server)那里要求政府归还欠他的一万元钱,议员让他在办公室等着,自己将问题反映给了总统,总统询问所有议员的意见,多数议员表示欠屁民的钱一定要还,于是总统发表声明,从国库中拿出一万元还债,国库总资产由100万变成99万。屁民乙拿到钱回去了(Client函数返回)。
情况三:
总统突然挂了,议员接二连三的发现联系不上总统,于是各自发表声明,推选新的总统,总统大选期间政府停业,拒绝屁民的请求。
作者:Ccww
链接:https://juejin.cn/post/6844903874587787277
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
7.3 ZAB
原子广播协议。作用在可用状态,也就是有leader。(队列+2PC(两阶段提交))
原子:全部成功活失败,没有中间状态。
广播:分布式多节点,全部都知道。只要过半通过就行。
队列:先进先出。
zk的数据状态在内存,磁盘来保存日志。
1个leader,2个follower
1.client向follower1发送创建请求
2.follower1收到请求,向leader发送请求
3.leader收到请求,获得最新事务zxid,在日志中记录ok
4.leader向follower1和follower发送创建请求
5.follower1收到创建请求,记录日志,回应ok
6.leader收到请求,此时包括自己已经有超过一半请求表示ok,那么这个创建事务成功
7.leader向follower表示事务成功,follower向cilent表示成功。
7.4 选举leader
自己会有myid,Zxid。
新的leader:
1.经验最丰富,Zxid、myid
2.过半通过的数据才是真数据,可用的Zxid
由于集群中Follower的数量应配置奇数个,数量应较少, 意思是zoo.cfg中除了 Follower数量+1(Leader)个zk外 剩下的节点后面都需加 ":oberser"么?
若集群发生变动,那么Observer有资格竞选Leader 或者 成为 Follower么?
答:剩下的节点后面都需加 ":oberser"么,是的;observer无权参与投票和竞选,就是一个观察者。
# 例子:四台机器,事务id相同,myid分别为2,6,4,8 选举哪个节点为Leader?
zk集群至少要有3个节点,假如这4个节点1、2、3、4先后启动,它们启动时的zxid都是一样的(设为0):
->节点1进入looking选举状态,给自己投票,发出(0,2),此时当选节点票数=1<节点总数的一半=2,故节点1仍保持looking状态继续等待选举
->节点2进入looking选举状态,给自己投票,发出(0,6),myid为6最大,2当选,但此时当选节点票数=2=节点总数的一半=2,故节点2仍保持looking状态继续等待选举
->节点3进入looking选举状态,给自己投票,发出(0,4),myid为6最大,2当选,且此时当选节点票数=3>节点总数的一半=2,故节点2当选leader
->节点4进入looking选举状态,给自己投票,发出(0,8),但此时集群leader已选出,所以节点4仍成为follwer
->选举完成后,leader的状态由looking变为leading,follower的状态由looking变为following
总的来说,启动阶段的选举当选的必然为位于中间的节点
7.5 watch
统一视图---目录树结构
watch观察节点是否有变化,create、change、delete、children。通过event有回调函数。
两个客户端之间产生连接,如果互相发送心跳来监控,时效性不行。因此使用watch
7.6 回调实现
main
package com.xqm.zookeeper.config;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.util.ObjectUtils;
public class ZkConfig {
ZooKeeper zooKeeper;
@Before
public void conn() {
zooKeeper = ZkUtil.getZk();
}
@After
public void close() {
try {
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void getConf() {
WatcherUtil watcherUtil = new WatcherUtil();
watcherUtil.setZooKeeper(zooKeeper);
ReceiveDataConf dataConf = new ReceiveDataConf();
watcherUtil.setDataConf(dataConf);
watcherUtil.await();
while (true){
if ("".equals(watcherUtil.getDataConf().getConf())){
System.out.println("配置已丢失");
watcherUtil.await();
}else {
// System.out.println(watcherUtil.getDataConf().getConf());
System.out.println(dataConf.getConf());
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ZKUtil
package com.xqm.zookeeper.config;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class ZkUtil {
private static ZooKeeper zk;
private static String path="118.195.208.143:2181,124.223.81.248:2181/testConf";
private static CountDownLatch countDownLatch=new CountDownLatch(1);
private static Watcher watcher=new DefaultWatch(countDownLatch);
public static ZooKeeper getZk(){
try {
zk=new ZooKeeper(path,1000,watcher);
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
return zk;
}
}
DefaultWatch
package com.xqm.zookeeper.config;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.util.concurrent.CountDownLatch;
public class DefaultWatch implements Watcher {
CountDownLatch countDownLatch;
public DefaultWatch() {
}
public DefaultWatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.toString());
switch (watchedEvent.getState()) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
countDownLatch.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
default:
break;
}
}
}
ReceiveDataConf
package com.xqm.zookeeper.config;
public class ReceiveDataConf {
private String conf;
public String getConf() {
return conf;
}
public void setConf(String conf) {
this.conf = conf;
}
}
WatcherUtil
package com.xqm.zookeeper.config;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.CountDownLatch;
public class WatcherUtil implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
ZooKeeper zooKeeper;
ReceiveDataConf dataConf;
CountDownLatch countDownLatch = new CountDownLatch(1);
public ReceiveDataConf getDataConf() {
return dataConf;
}
public void setDataConf(ReceiveDataConf dataConf) {
this.dataConf = dataConf;
}
public ZooKeeper getZooKeeper() {
return zooKeeper;
}
public void setZooKeeper(ZooKeeper zooKeeper) {
this.zooKeeper = zooKeeper;
}
// 节点监控回调函数
@Override
public void process(WatchedEvent watchedEvent) {
switch (watchedEvent.getType()) {
case None:
break;
case NodeCreated:
// 节点创建的时候要调用一次,否则会阻塞,不继续进行下去
zooKeeper.getData("/Appconf", this, this, "aaa");
break;
case NodeDeleted:
// 容忍性,节点被删除
dataConf.setConf("");
System.out.println("节点已删除");
countDownLatch=new CountDownLatch(1);
break;
case NodeDataChanged:
// 回调 processResult(int i, String s, Object o, byte[] data, Stat stat)
zooKeeper.getData("/Appconf", this, this, "aaa");
break;
case NodeChildrenChanged:
break;
default:
break;
}
}
// statCallback
// i:返回状态码 s:路径 o:传递的ctx,标识
@Override
public void processResult(int i, String s, Object o, Stat stat) {
// 节点存在,则继续回调dataCallback
if (!ObjectUtils.isEmpty(stat)) {
zooKeeper.getData("/Appconf", this, this, "aaa");
}else {
dataConf.setConf("");
}
}
/**
* DataCallback
*
* @param i
* @param s
* @param o
* @param data
* @param stat
*/
@Override
public void processResult(int i, String s, Object o, byte[] data, Stat stat) {
//
System.out.println("data:"+new String(data));
if (!ObjectUtils.isEmpty(data)) {
dataConf.setConf(new String(data));
countDownLatch.countDown();
}
}
public void await() {
zooKeeper.exists("/Appconf", this, this, "ABC");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
7.7 分布式锁
客户端自己加锁,锁的是自己的jvm,不能锁共享的资源。zk实现分布式锁,对一个资源进行加锁。
1.争抢锁,只有一个人能获得锁
2.获得锁的人,使用临时节点(session),自己挂掉,不会造成死锁
3.获得锁的人成功后,释放锁
4.其他人怎么监控这把锁
1.主动轮询,心跳,看节点是否存在,缺点就是延迟、有压力
2.使用watch节点,解决延迟.缺点是通信上有压力,某一时刻只能有一把锁,抢夺锁
3.使用序列节点+watch.使用watch序列的前一个人,最小的获得锁。一旦最小的释放锁,成本是zk只给第二个发事件