ZK实现分布式锁

喜夏-厌秋 提交于 2020-02-25 23:47:46

摘要

实现思路:

  1. 先去判断锁目录是否存在,不存在则创建锁目录及锁节点,存在则检查是否是自己持有锁,是自己持有则返回成功,不是自己持有则创建自己的锁节点。

  2. 去检查是否是自己持有锁,是自己持有锁,则返回成功;不是自己持有锁,则侦听上一个节点的状态变化,直到超时为止;上一个节点状态发生变化时,接着去检查是否轮到自己持有锁,依次循环。

Redis分布式锁的差异点对比:

  1. 本文实现的zk分布式锁是公平锁,上面redis实现的分布式锁是非公平锁。(redis也能实现公平锁,但是上文的redis锁没有实现)
  2. 本文实现的zk分布式锁不会出现业务超时导致锁失效的问题,上面的redis锁会出现这个问题。(redis锁能通过异步线程实现锁自动延时,但是上文的redis锁没有实现)
  3. 本文的zk锁跟上文的redis锁都支持可重入
  4. 比较下来,redis锁的性能更高一些

分布式锁源码

maven依赖

			<dependency>
				<groupId>com.101tec</groupId>
				<artifactId>zkclient</artifactId>
				<version>0.10</version>
			</dependency>
			<dependency>
				<groupId>org.projectlombok</groupId>
				<artifactId>lombok</artifactId>
				<version>1.18.4</version>
				<scope>provided</scope>
			</dependency>
			<dependency>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
				<version>19.0-rc2</version>
			</dependency>

lombok、guava这些,不需要可以去掉

StringZkSerializer

zk上data序列化用的,用来做锁可重入。

package com.imassbank.inf.common.lock.zk;

import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class StringZkSerializer implements ZkSerializer {

	@Override
	public byte[] serialize(Object data) throws ZkMarshallingError {
		try{
			return ((String)data).getBytes("UTF-8");
		}catch(Exception e){
			log.error("zk分布式锁序列化异常",e);
		}
		return null;
	}

	@Override
	public Object deserialize(byte[] bytes) throws ZkMarshallingError {
		try{
			return new String(bytes,"UTF-8");
		}catch(Exception e){
			log.error("zk分布式锁反序列化异常",e);
		}
		return null;
	}

}

ZkClientExt

在原生的zkclient上包装一下,简化client初始化

package com.imassbank.inf.common.lock.zk;

import org.I0Itec.zkclient.ZkClient;

public class ZkClientExt extends ZkClient {
	private static final Integer sessionTimeout = 5 *1000;
	private static final Integer connectionTimeout = 5 *1000;
	

	public ZkClientExt(String zkServers, int sessionTimeout, int connectionTimeout) {
		super(zkServers, sessionTimeout, connectionTimeout, new StringZkSerializer());
	}
	
	public ZkClientExt(String zkServers) {
		super(zkServers, sessionTimeout, connectionTimeout, new StringZkSerializer());
	}
	
}

ZKBaseLock

分布式锁的具体实现

package com.imassbank.inf.common.lock.zk;

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

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.apache.commons.collections.CollectionUtils;

import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public abstract class ZKBaseLock {

	private static final String BASE_PATH = "/DISTRIBUTE_LOCK";
	private static final String SEPERATOR = "/";
	protected ZkClientExt client;
	protected String lockPath;// 当前锁路径
	protected String lockFullPath; // 当前锁的完整路径  lockFullPath = lockPath/lockName;
	protected String mySequence;// 当前锁临时节点名称
	protected String mySequencePath; // 当前锁的完整路径  mySequencePath = lockPath/mySequence;
	
	protected String lockName; // 锁名称
	protected String uuid;

	public ZKBaseLock(ZkClientExt client,  String lockName) {
		this.client = client;
		this.lockPath = BASE_PATH.concat(SEPERATOR).concat(lockName);
		this.lockFullPath = lockPath.concat(SEPERATOR).concat(lockName);
		this.lockName = lockName;
		this.uuid = UUID.randomUUID().toString();
	}

	/**
	 * 获取锁
	 * @param waitTimeMills
	 * @return
	 * @throws Exception
	 */
	protected synchronized boolean tryLock(long waitTimeMills) throws Exception {
		final long startMillis = System.currentTimeMillis();

		try {
			if(isMyLock()){
				return Boolean.TRUE;
			}
			return  tryToLock(startMillis, waitTimeMills);
		} catch (ZkNoNodeException e) { // 捕获这个异常
			log.error("获取锁异常",e);
		}
		return Boolean.FALSE;
	}

	/**
	 *  尝试获取锁
	 * @param startMillis  创建锁的毫秒 时刻
	 * @param millisToWait 锁最长等待的毫秒数
	 * @return
	 * @throws Exception
	 */
	private boolean tryToLock(long startMillis, Long millisToWait) throws Exception {
		boolean haveTheLock = false;
		boolean doDelete = false;

		try {
			while (!haveTheLock) {
				List<String> children = getSortedChildren();
				int ourIndex = children.indexOf(mySequence);
				if (ourIndex < 0) { // 网络抖动,获取到的子节点列表里可能已经没有自己了
					log.error("所等待时,没有找到当前线程的临时节点。当前锁:{},当前线程的临时节点:{}",lockName,mySequence);
					throw new ZkNoNodeException("没有找到当前锁的临时节点: " + mySequence);
				}
				
				// 如果是第一个,代表自己已经获得了锁
				boolean isGetTheLock = ourIndex == 0?Boolean.TRUE:Boolean.FALSE;
				if (isGetTheLock) {
					return  true;
				}

				// 没有获取到锁,watch比我们次小的那个节点
				String preLockSequence = isGetTheLock ? null : children.get(ourIndex - 1);
				String preLockSequencePath = lockPath.concat("/").concat(preLockSequence);
				final CountDownLatch latch = new CountDownLatch(1);
				
				// 定义上一个锁节点的侦听事件,删除事件,则countDown
				final IZkDataListener previousListener = new IZkDataListener() {
					public void handleDataDeleted(String dataPath) throws Exception {
						latch.countDown(); // 删除后结束latch上的await
					}
					public void handleDataChange(String dataPath, Object data) throws Exception {
					}
				};

				try {
					// 订阅次小顺序节点的删除事件,如果节点不存在会出现异常
					client.subscribeDataChanges(preLockSequencePath, previousListener);
					if (millisToWait != null) {
						millisToWait -= (System.currentTimeMillis() - startMillis);
						startMillis = System.currentTimeMillis();
						if (millisToWait <= 0) {
							doDelete = true; // 超时了,则删除当前锁节点
							break;
						}
						latch.await(millisToWait, TimeUnit.MICROSECONDS); // 在latch上await
					} else {
						latch.await(); // 在latch上await
					}

					// 结束latch上的等待后,继续while重新来过判断自己是否第一个顺序节点
				} catch (ZkNoNodeException e) {
					log.error("锁等待异常",e);
					throw new ZkNoNodeException("订阅锁上一个节点异常");
				} finally {
					client.unsubscribeDataChanges(preLockSequencePath, previousListener);
				}
			}
		} catch (Exception e) {
			log.error("锁等待异常",e);
			doDelete = true;
			throw new RuntimeException("锁等待异常");
		} finally {
			if (doDelete) {
				deleteMySequencePath();
			}
		}
		return haveTheLock;
	}
	

	private String getLockNodeNumber(String str, String lockName) {
		int index = str.lastIndexOf(lockName);
		if (index >= 0) {
			index += lockName.length();
			return index <= str.length() ? str.substring(index) : "";
		}
		return str;
	}

	private List<String> getSortedChildren() throws Exception {
		try {
			List<String> children = client.getChildren(lockPath);
			if(CollectionUtils.isEmpty(children)){
				return Lists.newArrayList();
			}
			Collections.sort(children, new Comparator<String>() {
				public int compare(String lhs, String rhs) {
					return getLockNodeNumber(lhs, lockName).compareTo(getLockNodeNumber(rhs, lockName));
				}
			});
			return children;
		} catch (ZkNoNodeException e) {
			log.error("获取children 异常");
		}
		return Lists.newArrayList();
	}

	protected void releaseLock() {
		try{
			deleteMySequencePath();
		}catch(Exception e){
			log.error("释放锁异常",e);
		}
	}
	
	/**
	 * 创建临时顺序节点
	 * @param client
	 * @param path
	 * @return  mySequencePath
	 * @throws Exception
	 */
	private boolean isMyLock() throws Exception {
		try{
			if(!client.exists(lockPath)){
				client.createPersistent(lockPath, true);
				mySequencePath = client.createEphemeralSequential(lockFullPath, uuid);
				mySequence = mySequencePath.substring(lockPath.length()+1);
				return Boolean.FALSE;
			}
			List<String> children = getSortedChildren();
			if(!CollectionUtils.isEmpty(children)){
				// 如果第一个节点的data是当前锁的uuid,则可以进入,达到可重入的目标
				String data = client.readData(this.lockPath.concat(SEPERATOR).concat(children.get(0)), Boolean.TRUE);
				if(uuid.equalsIgnoreCase(data)){
					return Boolean.TRUE;
				}
			}
			mySequencePath = client.createEphemeralSequential(lockFullPath, uuid);
			mySequence = mySequencePath.substring(lockPath.length()+1);
			
		}catch(Exception e){
			log.error("创建分布式锁,初始化节点失败",e);
			throw new RuntimeException();
		}
		return Boolean.FALSE;
	}
	
	/**
	 * 刪除当前锁的临时节点,如果是最后一个临时节点,则删除整个锁目录
	 * @param 
	 * @throws Exception
	 */
	private void deleteMySequencePath() throws Exception {
		client.delete(mySequencePath);
		if(client.countChildren(lockPath) <=0){
			client.delete(lockPath);
		}
	}
}

ZKLock

最终锁

package com.imassbank.inf.common.lock.zk;

import java.io.IOException;

public class ZKLock extends ZKBaseLock{
	
    
    public ZKLock(ZkClientExt client, String lockName){
        super(client,lockName);
    }

    // 获取锁
    public synchronized void acquire() throws Exception {
        if ( !tryLock(-1) ) {
            throw new IOException("连接丢失!不能获取锁!" + super.lockName);
        }
    }

    /**
     * 最长等待的毫秒数
     * @param waitTimeMills
     * @return
     * @throws Exception
     */
    public boolean acquire(long waitTimeMills) throws Exception {
        return tryLock(waitTimeMills);
    }

    /**
     * 释放锁
     * @throws Exception
     */
    public void release() {
        releaseLock();
    }


}

功能测试

package com.imassbank.inf.common.lock.zk;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Main {
	private static final NameValuePair THREAD1 = new BasicNameValuePair("thread1","order1_");
	private static final NameValuePair THREAD2 = new BasicNameValuePair("thread2","order1_");
	private static final NameValuePair THREAD3 = new BasicNameValuePair("thread3","order1_");

	public static void main(String[] args) {

		final ZkClientExt client = new ZkClientExt("10.201.9.25:2181", 5000, 5000);

//		// 锁节点数据检验测试
//		ZKLock lock = new ZKLock(client, THREAD1.getValue());
//		try{
//			System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//			if(lock.acquire(10 *1000)){
//				System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//			}
//		}catch(Exception e){
//			
//		}finally{
//			lock.release();
//			System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//		}
		
		
		// 锁可重入验证
//		ZKLock lock = new ZKLock(client, THREAD1.getValue());
//		try{
//			System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//			if(lock.acquire(10 *1000)){
//				System.out.println("第一次获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//				if(lock.acquire(10 *1000)){
//					System.out.println("第二次获取到锁,,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//					Thread.sleep(5*1000L);
//				}
//			}
//		}catch(Exception e){
//			
//		}finally{
//			lock.release();
//			System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//		}
		
		

	
		
		//并发测试
//		Thread t1 = new Thread(
//				new Runnable(){
//
//					@Override
//					public void run() {
//						ZKLock lock = new ZKLock(client, THREAD1.getValue());
//						try{
//							System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//							if(lock.acquire(10 *1000)){
//								System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//								Thread.sleep(5*1000L);
//							}
//						}catch(Exception e){
//							
//						}finally{
//							lock.release();
//							System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//						}
//					}
//				});
//		t1.setName(THREAD1.getName());
//		t1.start();
//		
//		Thread t2 = new Thread(
//				new Runnable(){
//
//					@Override
//					public void run() {
//						ZKLock lock = new ZKLock(client, THREAD2.getValue());
//						try{
//							System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//							if(lock.acquire(10 *1000)){
//								System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//								Thread.sleep(5*1000L);
//							}
//						}catch(Exception e){
//							
//						}finally{
//							lock.release();
//							System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//						}
//					}
//				});
//		t2.setName(THREAD2.getName());
//		t2.start();
		
		
		//公平锁测试 把 mySequence 打印出来
//		Thread t1 = new Thread(
//				new Runnable(){
//
//					@Override
//					public void run() {
//						ZKLock lock = new ZKLock(client, THREAD1.getValue());
//						try{
//							System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//							if(lock.acquire(30 *1000)){
//								System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//								Thread.sleep(5*1000L);
//							}
//						}catch(Exception e){
//							
//						}finally{
//							lock.release();
//							System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD1.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//						}
//					}
//				});
//		t1.setName(THREAD1.getName());
//		t1.start();
//		
//		Thread t2 = new Thread(
//				new Runnable(){
//
//					@Override
//					public void run() {
//						ZKLock lock = new ZKLock(client, THREAD2.getValue());
//						try{
//							System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//							if(lock.acquire(30 *1000)){
//								System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//								Thread.sleep(5*1000L);
//							}
//						}catch(Exception e){
//							
//						}finally{
//							lock.release();
//							System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD2.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//						}
//					}
//				});
//		t2.setName(THREAD2.getName());
//		t2.start();
//		
//		
//		Thread t3 = new Thread(
//				new Runnable(){
//
//					@Override
//					public void run() {
//						ZKLock lock = new ZKLock(client, THREAD3.getValue());
//						try{
//							System.out.println("线程名称:"+Thread.currentThread().getName()+"  尝试获取锁:  " +THREAD3.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//							if(lock.acquire(30 *1000)){
//								System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD3.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//								Thread.sleep(5*1000L);
//							}
//						}catch(Exception e){
//							
//						}finally{
//							lock.release();
//							System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+"  获取到锁:  " +THREAD3.getValue()+ "    时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
//						}
//					}
//				});
//		t3.setName(THREAD3.getName());
//		t3.start();
		
		
		
		

	}

}


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!