ZooKeeper 案例-分布式锁

分布式锁

当一个节点正在读写数据时,禁止其他节点读写数据,避免造成数据同步错误。

创建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函数中出来,执行操作后解锁。

运行测试类,观察到节点先后使用节点。img

猜你喜欢

转载自blog.csdn.net/rfdjds/article/details/121044422