如何用zooKeeper实现一个分布式锁

461 阅读4分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

zookeeper的一些知识点

zookeeper.jpg

zookeeper的安装

1.准备工作:
#! 准备3个节点,要求配置好主机名称,服务器之间系统时间保持─致
#!注意/etc/hostname和/etc/hosts配置主机名称(在这个里我准备hkh221,hkh222,hkh223三节点)
#!特别主意以下操作3个节点要同时进行操作哦!#!注意关闭防火墙
启动防火墙 systemctl start firewalld
关闭防火墙 systemctl stop firewalld
重启防火墙 systemctl restart firewalld
查看防火墙状态 systemctl status firewalld
开机禁用防火墙 systemctl disable firewalld
#!本地访问: ping
2.上传zk到三台服务器节点
#!注意我这里解压到/usr/local下
2.1进行解压:tar zookeeper-3.4.6.tar.gz
2.2重命名:mv zookeeper-3.4.6 zookeeper
2.3修改环境变量:vim /etc/profile
#!这里要添加zookeeper的全局变量
export zOOKEEPER_HOME=/usr/local/zookeeper
export PATH=.:$ZOOKEEPER_HOME/bin
#2.4刷新环境变量:
source /etc/profile
#!2.5到zookeeper下修改配置文件:
#!  2.5.1首先到指定目录:
  cd /usr/local/zookeeper/conf
#!  2.5.2然后复制zoo_sample.cfg文件,复制后为zoo.cfg:
  mv zoo_sample.cfg zoo.cfg
#!  2.5.3 然后修改两处地方,最后保存退出:wq
#!    (1)修改数据的dir
      dataDir=/usr/local/zookeeper/data
#!    (2)修改集群地址
      server.0=hkh221:2888:3888
      server.1=hkh222:2888:3888
      server.2=hkh223:2888:3888
并且2.5.4 增加服务器标识配置,需要2步骤,第一是创建文件夹和文件,第二是添加配置内容:
#!(1)创建文件夹:
mkdir /usr/local/zookeeper-3.4.6/data
#!(2)创建文件myid 路径应该创建在/usr/local/zookeeper-3.4.6/data下面,如下:
vim /usr/local/zookeeper-3.4.6/data/ myid
注意这里每一台服务器的myid文件内容不同,分别修改里面的值为0,1,2;与我们之前的zoo.cfg配置文件里:
server.0,server.1,server.2 顺序相对应,然后保存退出;
#!2.7到此为止,Zookeeper集群环境大功告成!启动zookeeper命令
启动路径:/usr/local/zookeeper/bin(也可在任意目录,因为配置了环境变量)
执行命令:  zkServer.sh start
(注意这里3台机器都要进行启动,启动之后可以查看状态)

#!查看状态:
zkServer.sh status
(在三个节点上检验zk的mode,会看到一个1eader和俩个follower)
zkCli.sh   进入zookeeper客户端

#!根据提示命令进行操作:
查找: ls /ls /zookeeper
#!创建并赋值:
create /jacquesh zookeeper
获取:get /jacquesh
设值:set /jacquesh zookeeper1314
PS1:任意节点都可以看到zookeeper集群的数据─致性
PS2:创建节点有俩种类型:短暂(ephemeral)持久(persistent)

#! 设置zk开机启动
cd /etc/rc.d/init.d/
touch zookeeper
chmod 777 zookeeper
vim zookeeper
开机启动zookeeper脚本:
#!/bin/bash
        #chkconfig:2345 20 90
        #description :zookeeper
        #processname :zookeeper
        export JAVA_HOME=/usr/local/jdk1.8
        export PATH=$JAVA_HOME/bin:$PATH
        case $1 in
        start) /usr/local/zookeeper-3.4.6/bin/zkServer.sh  start;;
        stop) /usr/local/zookeeper-3.4.6/bin/zkServer.sh   stop;;
        status) /usr/local/zookeeper-3.4.6/bin/zkServer.sh  status;;
        restart) /usr/local/zookeeper-3.4.6/bin/zkServer.sh restart;;
        *) echo  "require start|stop|status|restart";;
esac

#!开机启动配置:
chkconfig zookeeper on
#!验证:
chkconfig --add zookeeper
chkconfig --list zookeeper

#! 启动/停止zookeeper服务
service zookeeper start/stop

#! 把zookeeper添加到开机启动里面
chkconfig --add zookeeper

#!查看添加的zookeeper是否在里面开机任务中
chkconfig --list zookeeper

代码

pom文件

    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.9</version>
    </dependency>

ZKUtils

public class ZKUtils {

    //zookeeper实例
    private ZooKeeper zk;
    //分布式业务锁业务目录

    private String rootPath = "/lock";
    //zookeeper地址
    private String address = "193.112.8.126:2181";
    //zookeeper实例化的闸门 防止未实例就调用
    private CountDownLatch createLatch = new CountDownLatch(1);
    //分布式线程锁
    private CountDownLatch threadLatch = new CountDownLatch(1);
    //watch
    private DefaultWatch watch;
    private Integer timeout = 10000;

    public static class Builder {
        /**
         * 使用构造者模式对主要数据对外暴露配置
         */
        private Integer timeout;
        private String address;
        private String rootPath;

        public Builder setTimeout(Integer timeout) {
            this.timeout = timeout;
            return this;
        }

        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }

        public Builder setRootPath(String rootPath) {
            this.rootPath = rootPath;
            return this;
        }

        public ZKUtils build() throws IOException, InterruptedException, KeeperException {

            ZKUtils zkUtils = new ZKUtils();
            if (address == null) address = zkUtils.address;
            if (timeout == null) timeout = zkUtils.timeout;
            if (rootPath == null) rootPath = zkUtils.rootPath;

            zkUtils.zk = new ZooKeeper(address, timeout,
                    /**
                     * 加这段是为了防止报错 没啥影响也乐意不加-。-。
                     */
                    event -> {
                        System.out.println(event.toString());
                    }
            );
            /**
             * 初始化DefaultWatch
             */
            zkUtils.watch = new DefaultWatch(zkUtils.createLatch, zkUtils.threadLatch, zkUtils.zk, rootPath);
            /**
             * 预先判断业务主节点是否存在 不存在创建
             */
            Stat stat = zkUtils.zk.exists(rootPath, zkUtils.watch);
            if (stat == null) {
                zkUtils.zk.create(rootPath,
                        rootPath.getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                /**
                 * 在创建指令发送后对进程加锁 防止node节点未创建就调用zookeeper的其他方法
                 */
                zkUtils.createLatch.await();
            }
            return zkUtils;
        }
    }

    public static Builder builder() {
        /**
         * 返回构建者实例
         */
        return new Builder();
    }

    /**
     * 关闭方法
     */
    public void closed() {
        if (zk != null) {
            try {
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @link DefaultWatch
     * 执行分布式锁操作
     * 实现过程坑看
     * @see  DefaultWatch#tryLock
     */
    public void tryLock() {
        watch.tryLock();
    }

    public void unLock() {
        watch.unLock();
    }
}

DefaultWatch

public class DefaultWatch implements Watcher, AsyncCallback.ChildrenCallback, AsyncCallback.Create2Callback {
    CountDownLatch createLatch;
    ZooKeeper zooKeeper;
    CountDownLatch threadLatch;
    String rootPath;
    private    String lockName;
    public DefaultWatch(CountDownLatch createLatch,CountDownLatch threadLatch,ZooKeeper zooKeeper,String rootPath) {
        this.threadLatch=threadLatch;
        this.createLatch = createLatch;
        this.zooKeeper=zooKeeper;
        this.rootPath=rootPath;
    }
    public void tryLock() {
        //重入
        try {
            /**
             * 加锁的操作就是在zookeeper创建这个线程的node节点
             * 使用CreateMode.EPHEMERAL_SEQUENTIAL 会有序号 
             */
            zooKeeper.create(rootPath+"/lock", Thread.currentThread().getName().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
            this, Thread.currentThread().getName());
            /**
             * 接下来就直接锁住线程就ok
             * 等待 zookeeper 回调唤醒线程就ok
             * 具体回调可看
             * @see DefaultWatch#processResult(int, String, Object, String, Stat)
             */
            threadLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()){
            case None :
                System.out.println("None");
                break;
            case  NodeCreated:
                if (event.getPath().equals(rootPath)){
                    System.out.println(event.getPath()+"节点创建");
                    createLatch.countDown();
                    System.out.println("放行");
                }
                break;
            case  NodeDeleted:
                System.out.println("NodeDeleted");
                break;
            case  NodeDataChanged:
                System.out.println("NodeDataChanged");
                break;
            case  NodeChildrenChanged:
                System.out.println("NodeChildrenChanged");
                 zooKeeper.getChildren(rootPath, this,this,"");

                break;
            case  DataWatchRemoved:
                System.out.println("DataWatchRemoved");
                break;
            case  ChildWatchRemoved:
                System.out.println("ChildWatchRemoved");
                break;

        }

    }

    /**
     *请求所有子节点回调的功能就是对所有的node节点进行排序 
     * 选择最小的那一个进行锁的放开
     * 
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children) {
        if(children!=null&&children.size()>0){
            Collections.sort(children);
            if(lockName.equals(rootPath+"/"+children.get(0))){
                threadLatch.countDown();
            }
        }else{
            /**
             * 如果业务根节点没有子节点 就只能说明所有任务都完成了
             */
            System.out.println("所有任务执行完成");
        }

    }

    /**
     *创建节点后的回调
     */
    @Override
    public void processResult(int rc, String path, Object ctx, String name, Stat stat) {
        //每个线程启动后创建锁,然后get锁目录的所有孩子,不注册watch在锁目录
           System.out.println(ctx.toString()+" create path: "+ name);
        /**
         * 创建节点后将创建的节点名称赋值给锁名称
         * 接下来获取业务根目录的所有子节点
         * 只是将请求过去 还是得等待回调
         * 请求查看子节点的回调可看
         * @see DefaultWatch#processResult(int, String, Object, List) 
         */
        lockName = name;
        zooKeeper.getChildren(rootPath, this, this, ctx );
    }

    /**
     * 释放锁操作的 就是将这个子节点给删了
     * 删除的时候也会进行回调
     * 回调的时候所有线程都会去getChildren 判断自己是否为获得锁的线程
     * 回调可以看
     * @see DefaultWatch#process(WatchedEvent) 
     */
    public void unLock() {
        try {
            zooKeeper.delete(lockName,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}