基于原生Zookeeper的分布式锁(基于临时顺序节点)

原理很简单,就是根据创建的顺序节点的ID来对访问进行排序,形成一个等待链表,后面的节点等待前面的节点完成(即监听到delete事件)

直接贴代码:

package com.xycode.zkUtils.lock;

import com.xycode.zkUtils.listener.SimpleEventListener;
import com.xycode.zkUtils.listener.ZKListener;
import com.xycode.zkUtils.zkClient.ZKCli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: ZKDistributedLock
 * 基于Zookeeper的顺序分布式锁
 * @Author: xycode
 * @Date: 2019/10/30
 * Description:
 **/
public class ZKDistributedLock {
    private static final Logger logger= LoggerFactory.getLogger("myLogger");

    private String lockPath;//顺序锁的父路径
    private String curID;//当前ZKCli占有的顺序ID
    private String prevID;//前一个顺序ID,当前ZKCLi要等待它

    private ZKCli zkCli;

    public ZKDistributedLock(String lockPath, ZKCli zkCli){
        this.lockPath=lockPath;
        this.zkCli=zkCli;
    }

    /**
     * 尝试获得锁的函数,会创建一个临时顺序节点,
     * 然后判断这个临时节点是否在等待队列的开头,若在开头,说明获锁成功,返回true
     * @return
     */
    public boolean tryLock(){
        try {
            //创建临时顺序节点,理论上不会出现竞争
            String[] tmp=zkCli.createEphemeralSeq(lockPath+"/1","").split("/");
            curID=tmp[tmp.length-1];//获得当前创建的顺序节点ID
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        //从最小的ID开始选,也就是最先创建顺序节点成功的那个ZKCli
        List ids = zkCli.getChildren(lockPath);
        String chosenId = Collections.min(ids);
//        System.out.println("-->" + ids.size());
        if(curID.equals(chosenId)) {//获得锁了
            logger.debug("Agent-{} acquire lock",curID);
            return true;
        }else{
            return false;
        }
    }

    /**
     * 等待锁的函数,当tryLock()没能获得锁时调用这个函数
     * 具体操作就是注册一个监听器,用于监听前一个顺序ID对应的路径,监听其删除事件,在这之前一直会阻塞等待
     */
    public void waitLock(){
        final CountDownLatch[] countDownLatch={new CountDownLatch(1)};
        //创建监听器对象
        ZKListener listener=new SimpleEventListener() {
            @Override
            public void NodeDeletedHandler(WatchedEvent event) {
//                System.out.println("--delete--");
                if(countDownLatch[0]!=null)
                    countDownLatch[0].countDown();
            }

        };
        //找到prevID
        List ids=zkCli.getChildren(lockPath);
        Collections.sort(ids);
        for(int i=0;i 2 -> 3(3意外挂掉了) -> curID -> ...,即变成了1 -> 2 ->   -> curID -> ...
            //这时curID是会监听到删除事件的,但是前面实际上还有ZKCli在等待,
            //再次waitLock()就是想变成这样1 -> 2 -> curID -> ...
            waitLock();
            logger.warn("[Fix chainBroken Exception]: try waitLock again!");
        }else{
            logger.debug("Agent-{} acquire lock",curID);
        }
    }

    /**
     * 加锁操作
     */
    public void lock(){
        if(!tryLock()){//每个尝试lock的线程,都会先tryLock一下,通过创建的顺序节点来确定是否获得lock
            waitLock();//lock失败了就wait,等待前一个节点delete,并且注意ChainBroken的异常情况
        }
    }

    /**
     * 解锁操作,就是删除当前占用的顺序节点
     */
    public void unlock(){
        final String curPath=lockPath+"/"+curID;
        try {
            if(zkCli.exists(curPath))
                zkCli.delete(curPath);
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        logger.debug("Agent-{} release lock",curID);
        zkCli.close();
    }

    //test
    public static void main(String[] args) {
        String ZKC_ADDRESS="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
        ZKCli zkCli=new ZKCli(ZKC_ADDRESS);
        String path="/seqLockPath";
        if(!zkCli.exists(path)) {
            try {
                //先创建一个永久节点
                zkCli.createPersistent(path, "", ZooDefs.Ids.OPEN_ACL_UNSAFE);
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }
        zkCli.close();
        Thread[] t=new Thread[10];
        for(int i=0;i {
                ZKCli zkCli1 =new ZKCli(ZKC_ADDRESS);
                zkCli1.unregisterDefaultListener();
                ZKDistributedLock lock=new ZKDistributedLock(path, zkCli1);
                //tip: 用法
                try{
                    lock.lock();
                    //zkCli此时获得了锁,可以做一些独占的事情
                    System.out.println("Agent-" + lock.curID + " working...");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }finally {
                    lock.unlock();
                }
            });
        }
        for (Thread thread : t) {
            thread.start();
        }
    }
}

输出:

Agent-10000000084 working...
2020-02-23 23:47:23,731 [DEBUG][Thread-1] 10000000090 waiting for 10000000089 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,732 [DEBUG][Thread-2] 10000000089 waiting for 10000000088 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,733 [DEBUG][Thread-4] 10000000087 waiting for 10000000086 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,734 [DEBUG][Thread-6] 10000000086 waiting for 10000000085 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,734 [DEBUG][Thread-10] 10000000091 waiting for 10000000090 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,734 [DEBUG][Thread-7] 10000000085 waiting for 10000000084 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,734 [DEBUG][Thread-8] 10000000092 waiting for 10000000091 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,735 [DEBUG][Thread-9] 10000000088 waiting for 10000000087 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:23,735 [DEBUG][Thread-3] 10000000093 waiting for 10000000092 (com.xycode.zkUtils.lock.ZKDistributedLock:94) 
2020-02-23 23:47:24,732 [DEBUG][Thread-5] Agent-10000000084 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:24,737 [DEBUG][Thread-7] Agent-10000000085 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000085 working...
2020-02-23 23:47:24,838 [DEBUG][Thread-5-EventThread] Thread-5-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:25,749 [DEBUG][Thread-7] Agent-10000000085 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:25,751 [DEBUG][Thread-6] Agent-10000000086 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000086 working...
2020-02-23 23:47:25,853 [DEBUG][Thread-7-EventThread] Thread-7-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:26,756 [DEBUG][Thread-6] Agent-10000000086 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:26,756 [DEBUG][Thread-4] Agent-10000000087 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000087 working...
2020-02-23 23:47:26,860 [DEBUG][Thread-6-EventThread] Thread-6-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:27,769 [DEBUG][Thread-4] Agent-10000000087 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:27,772 [DEBUG][Thread-9] Agent-10000000088 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000088 working...
2020-02-23 23:47:27,875 [DEBUG][Thread-4-EventThread] Thread-4-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:28,784 [DEBUG][Thread-9] Agent-10000000088 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:28,785 [DEBUG][Thread-2] Agent-10000000089 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000089 working...
2020-02-23 23:47:28,888 [DEBUG][Thread-9-EventThread] Thread-9-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:29,791 [DEBUG][Thread-2] Agent-10000000089 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:29,793 [DEBUG][Thread-1] Agent-10000000090 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000090 working...
2020-02-23 23:47:29,894 [DEBUG][Thread-2-EventThread] Thread-2-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:30,799 [DEBUG][Thread-1] Agent-10000000090 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:30,799 [DEBUG][Thread-10] Agent-10000000091 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000091 working...
2020-02-23 23:47:30,903 [DEBUG][Thread-1-EventThread] Thread-1-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:31,804 [DEBUG][Thread-10] Agent-10000000091 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:31,806 [DEBUG][Thread-8] Agent-10000000092 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000092 working...
2020-02-23 23:47:31,909 [DEBUG][Thread-10-EventThread] Thread-10-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:32,817 [DEBUG][Thread-8] Agent-10000000092 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:32,818 [DEBUG][Thread-3] Agent-10000000093 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115) 
Agent-10000000093 working...
2020-02-23 23:47:32,921 [DEBUG][Thread-8-EventThread] Thread-8-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 
2020-02-23 23:47:33,829 [DEBUG][Thread-3] Agent-10000000093 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139) 
2020-02-23 23:47:33,932 [DEBUG][Thread-3-EventThread] Thread-3-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39) 

可见达到了顺序锁的目的,当然这里实在同一个JVM中测试的,当处于不同机器中测试时,效果也会是一样的.

这里是ZKCli是我自己对Zookeeper进行封装的一个类,见我的github: https://github.com/xycodec/zkUtils, 这个分布式锁的实现也集成到了这个库中. 

 

你可能感兴趣的:(java,Zookeeper)