zookeeper 分布式 独占锁

参照apache官网zookeeper recipe 实现分布式独占锁。

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,对节点的写操作为原子性的,故可以实现分布式锁。  

apache官网zookeeper recipe:

Locks

Fully distributed locks that are globally synchronous, meaning at any snapshot in time no two clients think they hold the same lock. These can be implemented using ZooKeeeper. As with priority queues, first define a lock node.

Note

There now exists a Lock implementation in ZooKeeper recipes directory. This is distributed with the release -- src/recipes/lock directory of the release artifact.

Clients wishing to obtain a lock do the following:

  1. Call create( ) with a pathname of "_locknode_/guid-lock-" and the sequence and ephemeral flags set. The guid is needed in case the create() result is missed. See the note below.

  2. Call getChildren( ) on the lock node without setting the watch flag (this is important to avoid the herd effect).

  3. If the pathname created in step 1 has the lowest sequence number suffix, the client has the lock and the client exits the protocol.

  4. The client calls exists( ) with the watch flag set on the path in the lock directory with the next lowest sequence number.

  5. if exists( ) returns false, go to step 2. Otherwise, wait for a notification for the pathname from the previous step before going to step 2.


源码实现:

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName: DistributedLock
 * @Description: zookeeper 分布式独占锁
 * @author xiaoyababa
 * 
 */

public class DistributedLock implements Lock, Watcher{
	private static final Logger logger = LoggerFactory.getLogger(DistributedLock.class);
	private ZooKeeper zk;
    private String root = "/locks";//根
    private String lockName;//竞争资源的标志
    private String waitNode;//等待前一个锁
    private String myZnode;//当前锁
    private volatile CountDownLatch latch;//计数器
    private List<Exception> exception = new ArrayList<Exception>();
    private final static String splitStr = "_lock_";
    private final static long defaultWaitTime = 30*1000;
     
    /**
     * 创建分布式锁,使用前请确认config配置的zookeeper服务可用
     * @param config 127.0.0.1:2181
     * @param lockName 竞争资源标志,lockName中不能包含单词lock
     */
    public DistributedLock(String config, String lockName){
        this.lockName = lockName;
        // 创建一个与服务器的连接
         try {
            zk = new ZooKeeper(config, 60*1000, this);
            Stat stat = zk.exists(root, false);
            if(stat == null){
                // 创建根节点
                zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
            }
        } catch (IOException e) {
            exception.add(e);
        } catch (KeeperException e) {
            exception.add(e);
        } catch (InterruptedException e) {
            exception.add(e);
        }
    }
 
    /**
     * zookeeper节点的监视器
     */
    public void process(WatchedEvent event) {
        if(this.latch != null) {  
            this.latch.countDown();  
        }
    }
    
    /* 
     * 申请锁的默认时间是 defaultWaitTime ,超时后抛出LockException运行时异常
     */
    public void lock() {
        try {
            if(this.tryLock()){
                logger.debug("Thread " + Thread.currentThread().getId() + " " +myZnode + " get lock true");
                return;
            } else{
                waitForLock(waitNode, defaultWaitTime, TimeUnit.MILLISECONDS, true);//等待锁
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        } 
    }
 
    /* 
     * 非阻塞方式获取锁
     * 如果存在锁返回false
     * 通过判断返回值判断是否获取锁成功
     */
    public boolean tryLock() {
    	if(exception.size() > 0){
            throw new LockException(exception.get(0));
        }
        try {
            if(lockName.contains(splitStr))
                throw new LockException("lockName can not contains \\u000B");
            //创建临时子节点
            myZnode = zk.create(root + "/" + lockName + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            logger.debug(myZnode + " is created ");
            List<String> lockObjNodes = this.getChildrenListSort();
            logger.debug(myZnode + "==" + lockObjNodes.get(0));
            if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
                //如果是最小的节点,则表示取得锁
                return true;
            }
            //如果不是最小的节点,找到比自己小1的节点
            String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
            waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
        return false;
    }
    
    /* 
     * 非阻塞方式获取锁
     * 通过判断返回值判断是否获取锁成功
     */
    public boolean tryLock(long time, TimeUnit unit) {
        try {
            if(this.tryLock()){
                return true;
            }
            return waitForLock(waitNode,time,unit,false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
 
    /**
     * @param lower 次小节点
     * @param waitTime 等待时间
     * @param unit 时间单位
     * @param isBreak 超时是否抛出异常
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    private boolean waitForLock(String lower, long waitTime, TimeUnit unit,boolean isBreak) throws InterruptedException, KeeperException {
    	long beginTime = System.currentTimeMillis();
        Stat stat = zk.exists(root + "/" + lower,true);
        //判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
        if(stat != null){
            logger.debug("Thread " + Thread.currentThread().getId() + " waiting for " + root + "/" + lower);
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, unit);
            if(this.latch.getCount() == 1){
            	 this.latch = null;
            	 if(isBreak){
            		 throw new LockException("申请锁资源默认时间是:"+defaultWaitTime+"ms,等待锁超时");
            	 }else{
            		 return false;
            	 }
            }
            this.latch = null;
        }
        
        //取出所有lockName的锁
        List<String> lockObjNodes = this.getChildrenListSort();
        logger.debug(myZnode + "==" + lockObjNodes.get(0));
        if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
            //如果是最小的节点,则表示取得锁
            return true;
        }else{
        	 //如果不是最小的节点,找到比自己小1的节点
            String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
            waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
            long endTime = System.currentTimeMillis();
            waitTime = waitTime - (endTime - beginTime) ;
            if(waitTime <= 0) return false;
            return waitForLock(lower, waitTime, unit, false);//等待锁
        }
    }
    
    /**
     * 取出本次锁的所有子节点(升序)
     * @return
     * @throws LockException
     */
    private List<String> getChildrenListSort() throws KeeperException, InterruptedException{
		//取出所有子节点
		List<String> subNodes = zk.getChildren(root, false);
		//取出所有lockName的锁
		List<String> lockObjNodes = new ArrayList<String>();
		for (String node : subNodes) {
		    String _node = node.split(splitStr)[0];
		    if(_node.equals(lockName)){
		        lockObjNodes.add(node);
		    }
		}
		Collections.sort(lockObjNodes);
		return lockObjNodes;
    }
 
    public void unlock() {
        try {
            logger.debug("unlock " + myZnode);
            if(null != myZnode){
            	zk.delete(myZnode,-1);
            }
            myZnode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
 
    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }
 
    public Condition newCondition() {
        return null;
    }
     
    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }
 
}

测试源码:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

import com.niiwoo.utils.DistributedLock;

public class DistributedLockTest {

	private CyclicBarrier barrier = new CyclicBarrier(200);
	
	public static void main(String[] args){
		DistributedLockTest distributedLockTest = new DistributedLockTest();
		for(int i = 0;i<200;i++){
			ThreadTest threadTest = distributedLockTest.new ThreadTest(distributedLockTest.barrier,i);
			new Thread(threadTest).start();
		}
	}
	
	private class ThreadTest implements Runnable{
		private CyclicBarrier innerBarrier;
		private int aa = 0;
		public ThreadTest(CyclicBarrier barrier,int aa){
			this.innerBarrier = barrier;
			this.aa = aa;
		}
		@Override
		public void run() {
			try {
				innerBarrier.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			DistributedLock distributedLock = new DistributedLock("127.0.0.1:2181", "projectId2345");
			try {
				System.out.println("Thread"+aa+"开始获得锁");
				/*boolean flag = distributedLock.tryLock();*/
				boolean flag = distributedLock.tryLock(4000*1000,TimeUnit.MILLISECONDS);
				if(flag){
					System.out.println("Thread"+aa+"获得锁");
					System.out.println("Thread"+aa+"正在执行任务。。。");
					Thread.sleep(2*1000);
				}else{
					System.out.println("Thread"+aa+"未获得锁");
				}
			} catch (Exception e) {
				System.out.println("Thread"+aa+"获得锁报错");
			}
			finally{
				System.out.println("Thread"+aa+"释放锁");
				distributedLock.unlock();
			}
			
		}
		
	}
}

tips:运行时服务端抛出 
2016-02-17 15:19:18,993 [myid:] - WARN  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@357] - caught end of stream exception
EndOfStreamException: Unable to read additional data from client sessionid 0x152ee17a33600c8, likely client has closed socket
        at org.apache.zookeeper.server.NIOServerCnxn.doIO(NIOServerCnxn.java:228)
        at org.apache.zookeeper.server.NIOServerCnxnFactory.run(NIOServerCnxnFactory.java:208)
        at java.lang.Thread.run(Thread.java:745)

查看zookeeper源码,此异常为expect,故忽略。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值