Zookeeper分布式共享锁

在同一个服务内,如果我们要对同一个资源进行操作,可以通过synchronized关键字,Lock接口对多线程进行加锁。但是在分布式的环境下,这样的方式就不可控了,需要使用分布式的方式进行加锁。本文主要讲解了利用zookeeper来实现分布式共享锁。

程序流程如下:

 

程序逻辑

1.程序节点启动时到zookeeper上注册一个“短暂+序号”的znode,并且监听父节点

2.获取父节点下所有子节点,比较子节点的序号的大小

3.序号最小的子节点获取到锁,去访问资源,访问完后,删除自己的节点

4.其他程序会受到通知,则可以去zookeeper上获取锁

 

程序代码实现

1.Zookeeper客户端类,用于获取Zookeeper实例

public class ZookeeperClient {

    private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);

    /**
     * zookeeper集群的地址
     */
    private String hosts;

    /**
     * 连接通信时间,单位毫秒
     */
    private static int sessionTimeout = 2000;

    private ZooKeeper zooKeeper;

    /**
     * 获取zookeeper客户端
     * @param hosts zookeeper集群的地址
     * @return
     * @throws Exception
     */
    public ZooKeeper getZooKeeperClient(String hosts) throws Exception {
        return getZooKeeperClient(hosts, sessionTimeout);
    }

    /**
     * 获取zookeeper客户端
     * @param hosts zookeeper集群的地址
     * @param sessionTimeout 连接通信时间
     * @return
     * @throws Exception
     */
    public ZooKeeper getZooKeeperClient(String hosts, int sessionTimeout) throws Exception {
        zooKeeper = new ZooKeeper(hosts, sessionTimeout, event -> {
            logger.info("zookeeper is connected");
        });
        ZooKeeper.States states = zooKeeper.getState();
        // new ZooKeeper()的时候,会创建一个线程,用于和zookeeper中间件进行通信,此过程需要时间
        // 通过判断返回状态,确保已经正常连接zookeeper
        while (ZooKeeper.States.CONNECTED != states) {
            logger.info("zookeeper states is {}", states.name());
            Thread.sleep(100);
            states = zooKeeper.getState();
        }
        return zooKeeper;
    }
}

2.测试Zookeeper客户端类

public class ZookeeperClientTest {

    public static void main(String[] args) {
        ZookeeperClient zookeeperClient = new ZookeeperClient();
        try {
            String hosts = "bigdata-master:2181,bigdata-slaver1:2181,bigdata-slaver2:2181";
            ZooKeeper zooKeeper = zookeeperClient.getZooKeeperClient(hosts);
            List<String> childrenPaths = zooKeeper.getChildren("/", false);
            for(String path : childrenPaths) {
                System.out.println(path);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.Zookeeper分布式共享锁

/**
 * zookeeper分布式共享锁
 * 逻辑:
 * 1.程序节点启动时到zookeeper上注册一个“短暂+序号”的znode,并且监听父节点
 * 2.获取父节点下所有子节点,比较子节点的序号的大小
 * 3.序号最小的子节点获取到锁,去访问资源,访问完后,删除自己的节点
 * 4.其他程序会受到通知,则可以去zookeeper上获取锁
 **/
public class ZookeeperLock {

    private static final Logger logger = LoggerFactory.getLogger(ZookeeperLock.class);

    /**
     * zookeeper分布式共享锁的父目录
     */
    private String ROOT_LOCK_PATH = "/zookeeper_locks";

    /**
     * zookeeper分布式共享锁子目录
     */
    private String PRE_LOCK_NAME = "mylock_";

    /**
     * zookeeper分布式共享锁
     */
    private static ZookeeperLock zookeeperLock;

    private static Lock lock = new ReentrantLock();

    /**
     * zookeeper集群的地址
     */
    private String hosts;

    public ZookeeperLock(String hosts) {
        this.hosts = hosts;
    }

    /**
     * 获取zookeeper分布式锁实例,单例模式,一个服务只允许有一个zookeeper分布式锁实例
     * @return
     */
    public static ZookeeperLock getInstance(String hosts) {
        if(null == zookeeperLock) {
            lock.lock();
            try {
                if (null == zookeeperLock) {
                    zookeeperLock = new ZookeeperLock(hosts);
                }
            } finally {
                lock.unlock();
            }
        }
        return zookeeperLock;
    }

    /**
     * 获取分布式锁
     * @return
     * @throws Exception
     */
    public String lock() throws Exception {
        String lockPath = getZookeeperClient().create(ROOT_LOCK_PATH + '/' + PRE_LOCK_NAME, Thread.currentThread().getName().getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        logger.info("{} create lock path : {}", Thread.currentThread().getName(), lockPath);
        tryLock(lockPath);
        return lockPath;
    }

    /**
     * 1.获取锁父目录下的所有的子节点,并对子节点进行排序
     * 2.判断当前目录是否是序号最小的节点
     * 3.若是最小的节点,则返回true
     * 4.若不是最小的节点,则创建一个watch对象,用于监控lockPath的前一个节点
     * 5.获取上一个节点,若前一个节点不存在,则重新获取锁
     * 6.若前一个节点存在,则将watch线程挂起,等待重新唤醒
     * @param lockPath
     * @return
     */
    private boolean tryLock(String lockPath) throws Exception {
        // 1.获取锁父目录下的所有的子节点,并对子节点进行排序
        List<String> childrenPaths = getZookeeperClient().getChildren(ROOT_LOCK_PATH, false);
        Collections.sort(childrenPaths);
        // 2.判断当前目录是否是序号最小的节点
        int index = childrenPaths.indexOf(lockPath.substring(ROOT_LOCK_PATH.length() + 1));
        if(0 == index) {
            // 3.若是最小的节点,则返回true
            logger.info("{} get lock", lockPath);
            return true;
        } else {
            // 创建Watcher,监控lockPath的前一个节点
            Watcher watcher = new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    // 当节点被删除,唤醒所有等待的watcher对象,重新获取锁
                    logger.info("Received delete event, node path is {}", event.getPath());
                    synchronized (this) {
                        notifyAll();
                    }
                }
            };
            // 获取上一个节点路径
            String preLockPath = childrenPaths.get(index - 1);
            // 查询前一个目录是否存在,并且注册目录事件监听器,监听一次事件后即删除
            Stat stat = getZookeeperClient().exists(ROOT_LOCK_PATH + "/" + preLockPath, watcher);
            if(stat == null) {
                return tryLock(lockPath);
            } else {
                // 如果上一个节点存在,则watcher对象挂起,等待上一个节点被删除
                logger.info("{} wait for {}", Thread.currentThread().getName(), preLockPath);
                // 等待目录删除事件唤醒
                synchronized (watcher) {
                    watcher.wait();
                }
                // 当上一个节点被删除后,线程可以重新执行,重新tryLock
                return tryLock(lockPath);
            }
        }
    }

    /**
     * 释放锁
     * @param lockPath 锁的路径
     */
    public void unlock(String lockPath) {
        releaseLock(lockPath);
    }

    /**
     * 释放锁
     * 即删除目录
     */
    private void releaseLock(String lockPath) {
        try {
            getZookeeperClient().delete(lockPath, -1);
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        }
    }

    private ZooKeeper zooKeeper = null;

    /**
     * 获取zookeeper客户端
     * @return
     * @throws Exception
     */
    public ZooKeeper getZookeeperClient() throws Exception {
        if(null == zooKeeper) {
            lock.lock();
            try {
                if(null == zooKeeper) {
                    ZookeeperClient zookeeperClient = new ZookeeperClient();
                    zooKeeper = zookeeperClient.getZooKeeperClient(hosts);
                }
            } finally {
                lock.unlock();
            }
        }
        return zooKeeper;
    }

}

4.Zookeeper分布式共享锁类测试方法

public class ZookeeperLockTest {

    static class MsgConsumer extends Thread {
        @Override
        public void run() {
            String hosts = "bigdata-master:2181,bigdata-slaver1:2181,bigdata-slaver2:2181";
            ZookeeperLock zookeeperLock = ZookeeperLock.getInstance(hosts);
            try {
                String lockPath = zookeeperLock.lock();
                while(true) {
                    Thread.sleep(1000);
                    break;
                }
                zookeeperLock.unlock(lockPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MsgConsumer consumer1 = new MsgConsumer();
        MsgConsumer consumer2 = new MsgConsumer();
        MsgConsumer consumer3 = new MsgConsumer();
        MsgConsumer consumer4 = new MsgConsumer();
        MsgConsumer consumer5 = new MsgConsumer();
        MsgConsumer consumer6 = new MsgConsumer();
        consumer1.start();
        consumer2.start();
        consumer3.start();
        consumer4.start();
        consumer5.start();
        consumer6.start();
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值