分布式锁
当一个节点正在读写数据时,禁止其他节点读写数据,避免造成数据同步错误。
创建case2包,创建DistributeLock类,实现对线程加锁解锁
public class DistributeLock {
private final String connectString = "Hadoop003:2181,Hadoop004:2181,Hadoop005:2181";
private final int sessionTimeout = 2000;
private final ZooKeeper zK;
//为了增加代码健壮性,使用CountDownLatch,等待上面代码完全执行完毕
private CountDownLatch connectLatch = new CountDownLatch(1);
private CountDownLatch waitLatch = new CountDownLatch(1);
private String waitPath;
private String currentMode;
public DistributeLock() throws IOException, InterruptedException, KeeperException {
//连接zk
zK = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
//如果监听到的状态为连接中,程序释放,往下继续运行
connectLatch.countDown();
}
if (watchedEvent.getType()== Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
waitLatch.countDown();
//如果前一个结点删除,等待前一个节点使用完成的节点可以操作
}
{
}
}
});
//等待连接到zk
connectLatch.await();
//判断主节点是否存在
Stat stat = zK.exists("/locks", false);//获取节点的状态
if (stat == null) {
//主节点不存在,创建主节点
zK.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 对zk加锁(防止其他进入)
public void ZkLock() {
//创建临时在序号的节点
currentMode = null;
try {
currentMode = zK.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断创建节点是否是最小的节点,如果是获取到锁,如果不是监听其前一个节点
List<String> children = zK.getChildren("/locks", false);
//如果只有一个节点,直接获取锁;有多个节点,判断大小
if (children.size() == 1) {
return;
} else {
Collections.sort(children);//排序
//获取节点名称
String thisMode = currentMode.substring("/locks/".length());
//获取节点在集合中的位置
int index = children.indexOf(thisMode);
//判断节点是否存在,节点是否需要监听
if (index == -1) {
System.out.println("数据异常。");
//节点异常
} else if (index == 0) {
return;//节点是第一个不需要监听
} else {
waitPath = "/locks/" + children.get(index - 1);
zK.getData(waitPath, true, null);
//等待上一步执行完毕
waitLatch.await();//等待阻断结束
return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void UnZkLock(){
//删除节点
try {
zK.delete(currentMode,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
连接到zk,zklock函数用来对线程加锁,当一个线程发出连接请求到zk时,会等待connectionLatch被释放,才会进行连接,连接后执行此函数创建一个节点;判断此节点是否是第一个节点,不是第一个节点需要等待waitLatch被释放才能继续后面的操作。
unlock函数用来删除节点,即节点的下线
创捷一个测试类
public class Test {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
final DistributeLock lock1 = new DistributeLock();
final DistributeLock lock2 = new DistributeLock();
new Thread(new Runnable() {
@Override
public void run() {
lock1.ZkLock();
System.out.println("线程1启动, 获取到锁");
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1.UnZkLock();
System.out.println("线程1 释放锁");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
lock2.ZkLock();
System.out.println("线程2启动,获取到锁");
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.UnZkLock();
System.out.println("线程2 释放锁");
}
}).start();
}
}
创建两个线程,模拟当有两个服务器都要使用节点时的情况;当zklock执行完成后,代表获取了使用权,及对服务器加上了锁,执行操作后,解锁;监听器捕捉到节点下线,被阻断的另一个进程从zklock函数中出来,执行操作后解锁。
运行测试类,观察到节点先后使用节点。