基于zookeeper实现分布式锁

zookeeper实现原理

  • 创建zk上的一个临时节点。将这个临时节点作为zk的锁,然后客户端都要来获取这个锁。
  • 客户端A先对zk发起了加分布式锁的请求,如果临时节点不存在则走拿到锁的操作。如果此时创建的临时节点已经存在,
  • 则需要排队等待前个客户端释放锁。
  • 通过监听节点数据的变化(subscribeDataChanges),方法handleDataDeleted中当连接会话已结束,节点已被删除,释放锁,客户端将有机会创建临时节点。接着,客户端加锁之后,可能处理了一些代码逻辑,然后就会释放锁。将这个节点再次会被删除。
  • 删除那个节点之后,zk会负责通知监听这个节点的监听器,也就是通知其它客户端,会话已经结束,监听的那个节点被删除了,释放了锁。

创建maven工程引入jar

<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
	<dependency>
	    <groupId>com.101tec</groupId>
	    <artifactId>zkclient</artifactId>
	    <version>0.11</version>
	</dependency>

创建自定义Lock

package com.microservice.soa.zk;

/**
 * zk 自定义锁机制
 * @author jiajie
 *
 */
public interface ZkLock {
	
	/**
	 * 加锁
	 */
	public void lock();
	
	/**
	 * 释放锁
	 */
	public void unlock();
	
	/**
	 * 等待锁 释放
	 */
	public void waitlock();
	
}

创建ZKClientServer

package com.microservice.soa.zk;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.ZooKeeper;

/**
 * 
 * @author jiajie
 *
 */
public abstract class ZkDistributeLock {
	
	// 创建zk
	abstract boolean createZk();
	
}


***************************************************************


package com.microservice.soa.zk;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkException;
import org.I0Itec.zkclient.exception.ZkInterruptedException;
import org.I0Itec.zkclient.exception.ZkTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class ZKClientServer extends ZkDistributeLock implements ZkLock{
	private static Logger logger = LoggerFactory.getLogger(ZKClientServer.class);
	private  String zkUrl= null;
	private ZkClient zkClient = null;
	private String path= null;
	//1表示阻塞 0表示唤起 信号量
	CountDownLatch countDownLatch = new CountDownLatch(1);
	
	public ZKClientServer(String zkUrl, String path) {
		this.zkUrl = zkUrl;
		this.path = path;
		//连接Zookeeper
		 zkClient = new ZkClient(zkUrl);
	}

	/**
	 * 加锁
	 */
	@Override
	public void lock() {
		// TODO Auto-generated method stub
		if (createZk()) {
			logger.info(Thread.currentThread().getName()+"-获取锁成功!");
		}else {
			logger.info(Thread.currentThread().getName()+"-获取锁失败,进入等待!");
			//等待锁释放
			waitlock();
			lock();
		}
	}
	
	/**
	 * 释放锁
	 */
	@Override
	public void unlock() {
		// TODO Auto-generated method stub
		if (zkClient!=null) {
			logger.info("释放锁成功!");
			//关闭zk连接  释放锁
			zkClient.close();
		}
	}

	@Override
	boolean createZk() {
		// TODO Auto-generated method stub
		//创建临时节点,会话失效后删除
		try {
			zkClient.createEphemeral(path);
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
//			e.printStackTrace();
			//创建异常时代表 会话还未结束,临时节点还未被删除
			return false;
		} 
		//创建成功
		return true;
	}

	/**
	 * 实现了IZkDataListener接口的类,需要重新	
	 * 				handleDataDeleted(String path)方法(节点删除时触发)
	 * 				和handleDataChanges(String path, Object data)方法(节点数据改变时触发)。
	 * 	经测试,该接口只会对所监控的path的数据变化,子节点数据发送变化不会被监控到。
	 *
	 */
	@Override
	public void waitlock() {
		// TODO Auto-generated method stub
//		//subscribeDataChanges方法(只监听节点数据的变化)
//		zkClient.subscribeDataChanges(path, new IZkDataListener() {
//			/**
//			 * 节点删除时触发
//			 */
//			@Override
//			public void handleDataDeleted(String dataPath) throws Exception {
//				// TODO Auto-generated method stub
//				logger.info("连接会话已结束,节点已被删除!");
//				if (countDownLatch !=null) {
//					//信号量减一    0 释放锁
//					countDownLatch.countDown();
//				}
//			}
//			
//			/**
//			 * 节点数据改变时触发
//			 */
//			@Override
//			public void handleDataChange(String dataPath, Object data) throws Exception {
//				// TODO Auto-generated method stub
//				
//			}
//		});
		
		IZkDataListener iZkDataListener = new IZkDataListener() {
			
			/**
			 * 节点删除时触发
			 */
			@Override
			public void handleDataDeleted(String dataPath) throws Exception {
				// TODO Auto-generated method stub
				logger.info("连接会话已结束,节点已被删除!");
				if (countDownLatch !=null) {
					//信号量减一    0 释放锁
					countDownLatch.countDown();
				}
			}
			
			/**
			 * 节点数据改变时触发
			 */
			@Override
			public void handleDataChange(String dataPath, Object data) throws Exception {
				// TODO Auto-generated method stub
				
			}
		};
		//subscribeDataChanges方法(只监听节点数据的变化)注册监听通知		
		zkClient.subscribeDataChanges(path, iZkDataListener);	
		if (zkClient.exists(path)) {  //判断结点路径是否存在
			//节点路径信息不为空时创建信号量  初始为1
			countDownLatch = new CountDownLatch(1);
			try {
				//等待 阻塞
				countDownLatch.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			// 监听完毕后,移除事件通知
			zkClient.unsubscribeDataChanges(path, iZkDataListener);
		}
	}

	
}

生成订单工具类

package com.microservice.soa.commons.tool;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class Utils {
	
	 /**
     * 生成订单号
     * @return
     */
    public static String getOrderIdByTime() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
		String newDate = sdf.format(new Date());
		String result = "";
		Random random = new Random();
		for (int i = 0; i < 3; i++) {
			result += random.nextInt(10);
		}
		return newDate + result;
    }
}

测试代码

package com.microservice.soa.zk;

import com.microservice.soa.commons.tool.Utils;

public class TestZk implements Runnable {
	ZKClientServer zkServer = new ZKClientServer("127.0.0.1","/orderNo");
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			//获取锁
			zkServer.lock();
			String orderNo = Utils.getOrderIdByTime();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"生成订单:"+orderNo);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			//释放锁
			zkServer.unlock();
		}
		
	}
	
	public static void main(String[] args) {
		System.out.println("多线程生成orderNo");
		// OrderService orderService = new OrderService();
		for (int i = 0; i < 100; i++) {
			new Thread(new TestZk()).start();
		}
	}

	
}

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值