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();
}
}
}
运行结果