Zookeeper分布式锁原理笔记
zookeeper以树结构主要保存分布式协调信息状态
Zookeeper基础
znode结构
path 唯一路径
childNode 子节点
stat 状态属性,如创建时事务id,判断是否是临时节点等
type 节点类型,4种
data 数据,可有可无 bytes[]
create /yun1 "I am data1"
get /yun1 会返回节点数据 I am data1
create /yun1/yun2 创建yun1的子节点yun2
ls /yun1 会返回yun1下所有子节点,如yun2
get -s/yun1 返回yun1节点stat状态属性
4种节点类型
持久节点,默认
持久序号节点
临时节点,不能再有子节点,客户端关闭后会删除
临界序号节点,不能再有子节点,客户端关闭后会删除
create -s或-e分别指定使用序号节点和使用临时节点
序号节点:ZK会自动为给定节点名加上一个不断递增数字后缀
临时节点作用场景
yun1节点下有n个机器在执行
yun1会有n个临时子节点,当其中一个机器挂了,这台机器所属临时节点会删除
zookeeper分布式锁
场景:假设银行账户读取数据时候,不能修改账户数据 ,账户ID为333
流程
1 创建临时序号节点 account/333000001,其他人创建则33300000n
2 获取所有序号比自己小的节点
3 是否全部为读节点,是则跳4,否则代表前面有写节点在拿锁则跳5
4 获得锁成功
5 添加子节点更换成监听,监听触发等待获取锁
1 /333-R000001 获得锁
2 /333-R000002 获得锁
3 /333-W000003 等待锁,监听上一个节点
4 /333-R000004 等待锁,因为读4前面有了写3在等待,监听上一个节点
为什么设计监听上一个节点?
如果全让父节点通知所有子节点,会瞬间带来超大并发量,只监听上一个节点避免了这个问题
实现
public class Lock{
private String lockId;
private String path;
private boolean active;
构造函数,get和set方法略过
}
public class ZookeeperLock{
//创建临时序号节点
private ZkClient zkClient;
public ZookeeperLock(){
zkClient = new ZkClient("192.168.0.149:2181"//连接zk server,5000//session超时时间,20000//连接超时时间);
}
//获得锁
public Lock lock(String lockId, long timeout){
Lock lockNode = createLockNode(lockId);//创建还没拿锁的新临时节点
lockNode = tryActiveLock(lockNode);//尝试拿锁
if(!lockNode.isActive()//没激活成功,没拿到锁){
try{
synchronized(lockNode){
lockNode.wait(timeout);
}
}catch(~){
//抛出异常处理
}
}
return lockNode;
}
//激活锁
public Lock tryActiveLock(Lock lockNode){
//判断是否获得锁
//按照顺序获得所有排列好的子节点
List<String> list = zkClient.getChildren("/tuling-lock").stream().sorted().map(p->"/tuling-lock/"+p).collect(Collections.toList());
String firstPath = list.get(0);
if(firstPath.equals(lockNode.getPath())){
lockNode.setActive(true);
}else{//没拿到锁,添加上一个节点监听
String upNodePath = list.get(list.indexOf(lockNode.getPath())-1);
//实现对上一个节点监听
zkClient.subscribeDataChanges(upNodePath, new IZkDataListener(){
~
@Override
public void handleDataDeleted(~){
//节点删除了,再次尝试获得锁
Lock lock = tryActiveLock(lockNode);
synchronized(lockNode){
if(lock.isActive()){
lockNode.notify();//成功获得锁
}
zkClient.unsubscribeDataChanges(~);
}
}
}
)
}
return lockNode;
}
//释放锁
public void unlock(Lock lock){
}
//创建临时节点
public Lock createLockNode(String lockId){
//创建写锁,这里写死了
String path = zkClient.createEphemeralSequential("/tuling-lock"+lockId//path,"w"//data);
Lock lock = new Lock();
lock.setActive(false);
lock.setId(lockId);
lock.setPath(path);
return lock;
}
}