当一个节点正在读写数据时,禁止其他节点读写数据,避免造成数据同步错误。
创建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函数中出来,执行操作后解锁。
运行测试类,观察到节点先后使用节点。