1. 需求概览
有了Zookeeper的一致性文件系统,分布式锁的问题,变的容易
锁服务可以分为三类:独占锁、共享锁、时序锁
写锁:也就是独占锁,我独占这个资源,或者叫排他锁
读锁:对读加锁,可共享访问,释放锁之后才可以进行事务操作,也叫共享锁
时序锁:顾名思义,也就是控制时序的
对于第一类,我们讲Zookeeper上的一个节点(znode)看做是一把锁,通过create node 来进行实现,所有客户端都去创建/lock节点,最终创建成功的那个客户端,也就是拥有了这把锁,用完了,可以释放掉,这样,别人就可以创建了
对于第二类,/lock已经预先存在,所有的客户端在它下面创建临时顺序编号目录节点,和选择master一样,编号最小的获得锁,用完删除,依次有序
2. 独占锁
ZK实现独占锁有一个问题:那就是并发问题
ZK系统如果产生了这么一种情况:某个znode节点的数据变化非常的话,每次变化触发一次process回调!由于zk执行事务的时候,是串行单节点严格有序执行的,leader负责和这个事务的顺序执行,多个事件来不执行,上一个时间还没有执行,下一个触发动作,zk会忽略,影响其实并不是很大。只是他不执行吗,但是会有其他的执行!
package de.apps.lock;
import de.apps.动态上下线.ZookeeperConstant;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: de.apps.lock
* Version: 1.0
*
* @author qingzhi.wu 2020/8/8 20:43
*/
public class ZooKeeperDistributeSyncLock {
private static final String CURRENT_NODE = "hadoop01";
private static final Random random = new Random();
// 父子节点
private static final String LOCK_PARENT_NODE = "/parent_synclock";
private static final String LOCK_SUB_NODE = LOCK_PARENT_NODE + "/sub_sync_lock";
// 会话对象
private static ZooKeeper zk = null;
private static ArrayList<ACL> acls = ZooDefs.Ids.OPEN_ACL_UNSAFE;
private static final CreateMode CMP = CreateMode.PERSISTENT;
private static final CreateMode CME = CreateMode.EPHEMERAL;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
zk = new ZooKeeper(ZookeeperConstant.ZK_CONNECTS,ZookeeperConstant.TIMEOUT_MILL,new MyWatch());
countDownLatch.await();
if (zk.exists(LOCK_PARENT_NODE,false) == null ){
zk.create(LOCK_PARENT_NODE,LOCK_PARENT_NODE.getBytes(),acls,CMP);
}
zk.exists(LOCK_SUB_NODE, true);
try {
zk.create(LOCK_SUB_NODE, CURRENT_NODE.getBytes(),acls,CME);
handleBusiness(zk, CURRENT_NODE);
} catch (Exception e) {
System.out.println("锁已经被别人持有了,我等着吧");
}
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
public static void handleBusiness(ZooKeeper zooKeeper, String server) {
int sleepTime = 10000;
System.out.println(server + " is working .......... " + System.currentTimeMillis());
try {
// 线程睡眠0-4秒钟,是模拟业务代码处理所消耗的时间
Thread.sleep(random.nextInt(sleepTime));
// 模拟业务处理完成
zooKeeper.delete(LOCK_SUB_NODE, -1);
System.out.println(server + " is done --------" + + System.currentTimeMillis());
// 线程睡眠0-4秒, 是为了模拟客户端每次处理完了之后再次处理业务的一个时间间隔,
// 最终的目的就是用来打乱你运行的多台服务器抢注该子节点的顺序
Thread.sleep(random.nextInt(sleepTime));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
static class MyWatch implements Watcher {
@Override
public void process(WatchedEvent event) {
// 确保链接建立
if (countDownLatch.getCount() > 0 && event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("创建会话链接成功");
countDownLatch.countDown();
}
String path = event.getPath();
Event.EventType type = event.getType();
System.out.println(path + "\t" + type);
if (LOCK_SUB_NODE.equals(path) && Event.EventType.NodeDeleted == type){
//锁没了,我要大展宏图了
try {
zk.create(LOCK_SUB_NODE,CURRENT_NODE.getBytes(),acls,CreateMode.EPHEMERAL);
zk.exists(LOCK_SUB_NODE,true);
handleBusiness(zk,CURRENT_NODE);
} catch (Exception e) {
System.out.println("我没有抢到锁,等待下一次抢占");
}
}
try {
zk.exists(LOCK_SUB_NODE,true);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3. 时序锁
序号最小的执行
package de.apps.lock;
import de.apps.动态上下线.ZookeeperConstant;
import org.apache.zookeeper.*;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* Copyright (c) 2019 bigdata ALL Rights Reserved
* Project: learning
* Package: de.apps.lock
* Version: 1.0
*
* @author qingzhi.wu 2020/8/8 21:12
*/
public class ZooKeeperDistributeSequenceLock {
private static final String PARENT_NODE = "/parent_locks";
private static final String SUB_NODE = "/sub_sequence_lock";
private static String currentPath = "";
private static ZooKeeper zk = null;
public static void main(String[] args) throws Exception {
/**
* 1、拿到zookeeper链接
*/
zk = new ZooKeeper(ZookeeperConstant.ZK_CONNECTS,ZookeeperConstant.TIMEOUT_MILL,new MyWatch());
/**
* 2、查看父节点是否存在,不存在则创建
*/
Stat exists = zk.exists(PARENT_NODE, false);
if (exists == null) {
zk.create(PARENT_NODE, PARENT_NODE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 3、监听父节点
*/
zk.getChildren(PARENT_NODE,true);
//上来就要抢占锁,不然就没有监听事件了
currentPath = zk.create(PARENT_NODE + SUB_NODE, SUB_NODE
.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
Thread.sleep(Long.MAX_VALUE);
/**
* 5、关闭zk链接
*/
zk.close();
}
static class MyWatch implements Watcher {
@Override
public void process(WatchedEvent event) {
Event.EventType type = event.getType();
String path = event.getPath();
if (Event.EventType.NodeChildrenChanged == type && PARENT_NODE.equals(path)){
// 获取父节点的所有子节点, 并继续监听
List<String> childrenNodes = null;
try {
childrenNodes = zk.getChildren(PARENT_NODE, true);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 匹配当前创建的znode是不是最小的znode
Collections.sort(childrenNodes);
if ((PARENT_NODE + "/" + childrenNodes.get(0)).equals(currentPath)) {
// 处理业务
try {
handleBusiness(zk, currentPath);
currentPath = zk.create(PARENT_NODE + SUB_NODE, SUB_NODE
.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("not me");
}
}
}
}
public static void handleBusiness(ZooKeeper zk, String create) throws Exception {
Random random = new Random();
int sleepTime = 4000;
System.out.println(create + " is working .......... ");
// 线程睡眠0-4秒钟,是模拟业务代码处理所消耗的时间
Thread.sleep(random.nextInt(sleepTime));
// 模拟业务处理完成
zk.delete(currentPath, -1);
System.out.println(create + " is done --------");
// 线程睡眠0-4秒, 是为了模拟客户端每次处理完了之后再次处理业务的一个时间间隔,
// 最终的目的就是用来打乱你运行的多台服务器抢注该子节点的顺序
Thread.sleep(random.nextInt(sleepTime));
}
}