在同一个服务内,如果我们要对同一个资源进行操作,可以通过synchronized关键字,Lock接口对多线程进行加锁。但是在分布式的环境下,这样的方式就不可控了,需要使用分布式的方式进行加锁。本文主要讲解了利用zookeeper来实现分布式共享锁。
程序流程如下:
程序逻辑
1.程序节点启动时到zookeeper上注册一个“短暂+序号”的znode,并且监听父节点
2.获取父节点下所有子节点,比较子节点的序号的大小
3.序号最小的子节点获取到锁,去访问资源,访问完后,删除自己的节点
4.其他程序会受到通知,则可以去zookeeper上获取锁
程序代码实现
1.Zookeeper客户端类,用于获取Zookeeper实例
public class ZookeeperClient {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
/**
* zookeeper集群的地址
*/
private String hosts;
/**
* 连接通信时间,单位毫秒
*/
private static int sessionTimeout = 2000;
private ZooKeeper zooKeeper;
/**
* 获取zookeeper客户端
* @param hosts zookeeper集群的地址
* @return
* @throws Exception
*/
public ZooKeeper getZooKeeperClient(String hosts) throws Exception {
return getZooKeeperClient(hosts, sessionTimeout);
}
/**
* 获取zookeeper客户端
* @param hosts zookeeper集群的地址
* @param sessionTimeout 连接通信时间
* @return
* @throws Exception
*/
public ZooKeeper getZooKeeperClient(String hosts, int sessionTimeout) throws Exception {
zooKeeper = new ZooKeeper(hosts, sessionTimeout, event -> {
logger.info("zookeeper is connected");
});
ZooKeeper.States states = zooKeeper.getState();
// new ZooKeeper()的时候,会创建一个线程,用于和zookeeper中间件进行通信,此过程需要时间
// 通过判断返回状态,确保已经正常连接zookeeper
while (ZooKeeper.States.CONNECTED != states) {
logger.info("zookeeper states is {}", states.name());
Thread.sleep(100);
states = zooKeeper.getState();
}
return zooKeeper;
}
}
2.测试Zookeeper客户端类
public class ZookeeperClientTest {
public static void main(String[] args) {
ZookeeperClient zookeeperClient = new ZookeeperClient();
try {
String hosts = "bigdata-master:2181,bigdata-slaver1:2181,bigdata-slaver2:2181";
ZooKeeper zooKeeper = zookeeperClient.getZooKeeperClient(hosts);
List<String> childrenPaths = zooKeeper.getChildren("/", false);
for(String path : childrenPaths) {
System.out.println(path);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.Zookeeper分布式共享锁
/**
* zookeeper分布式共享锁
* 逻辑:
* 1.程序节点启动时到zookeeper上注册一个“短暂+序号”的znode,并且监听父节点
* 2.获取父节点下所有子节点,比较子节点的序号的大小
* 3.序号最小的子节点获取到锁,去访问资源,访问完后,删除自己的节点
* 4.其他程序会受到通知,则可以去zookeeper上获取锁
**/
public class ZookeeperLock {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperLock.class);
/**
* zookeeper分布式共享锁的父目录
*/
private String ROOT_LOCK_PATH = "/zookeeper_locks";
/**
* zookeeper分布式共享锁子目录
*/
private String PRE_LOCK_NAME = "mylock_";
/**
* zookeeper分布式共享锁
*/
private static ZookeeperLock zookeeperLock;
private static Lock lock = new ReentrantLock();
/**
* zookeeper集群的地址
*/
private String hosts;
public ZookeeperLock(String hosts) {
this.hosts = hosts;
}
/**
* 获取zookeeper分布式锁实例,单例模式,一个服务只允许有一个zookeeper分布式锁实例
* @return
*/
public static ZookeeperLock getInstance(String hosts) {
if(null == zookeeperLock) {
lock.lock();
try {
if (null == zookeeperLock) {
zookeeperLock = new ZookeeperLock(hosts);
}
} finally {
lock.unlock();
}
}
return zookeeperLock;
}
/**
* 获取分布式锁
* @return
* @throws Exception
*/
public String lock() throws Exception {
String lockPath = getZookeeperClient().create(ROOT_LOCK_PATH + '/' + PRE_LOCK_NAME, Thread.currentThread().getName().getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
logger.info("{} create lock path : {}", Thread.currentThread().getName(), lockPath);
tryLock(lockPath);
return lockPath;
}
/**
* 1.获取锁父目录下的所有的子节点,并对子节点进行排序
* 2.判断当前目录是否是序号最小的节点
* 3.若是最小的节点,则返回true
* 4.若不是最小的节点,则创建一个watch对象,用于监控lockPath的前一个节点
* 5.获取上一个节点,若前一个节点不存在,则重新获取锁
* 6.若前一个节点存在,则将watch线程挂起,等待重新唤醒
* @param lockPath
* @return
*/
private boolean tryLock(String lockPath) throws Exception {
// 1.获取锁父目录下的所有的子节点,并对子节点进行排序
List<String> childrenPaths = getZookeeperClient().getChildren(ROOT_LOCK_PATH, false);
Collections.sort(childrenPaths);
// 2.判断当前目录是否是序号最小的节点
int index = childrenPaths.indexOf(lockPath.substring(ROOT_LOCK_PATH.length() + 1));
if(0 == index) {
// 3.若是最小的节点,则返回true
logger.info("{} get lock", lockPath);
return true;
} else {
// 创建Watcher,监控lockPath的前一个节点
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
// 当节点被删除,唤醒所有等待的watcher对象,重新获取锁
logger.info("Received delete event, node path is {}", event.getPath());
synchronized (this) {
notifyAll();
}
}
};
// 获取上一个节点路径
String preLockPath = childrenPaths.get(index - 1);
// 查询前一个目录是否存在,并且注册目录事件监听器,监听一次事件后即删除
Stat stat = getZookeeperClient().exists(ROOT_LOCK_PATH + "/" + preLockPath, watcher);
if(stat == null) {
return tryLock(lockPath);
} else {
// 如果上一个节点存在,则watcher对象挂起,等待上一个节点被删除
logger.info("{} wait for {}", Thread.currentThread().getName(), preLockPath);
// 等待目录删除事件唤醒
synchronized (watcher) {
watcher.wait();
}
// 当上一个节点被删除后,线程可以重新执行,重新tryLock
return tryLock(lockPath);
}
}
}
/**
* 释放锁
* @param lockPath 锁的路径
*/
public void unlock(String lockPath) {
releaseLock(lockPath);
}
/**
* 释放锁
* 即删除目录
*/
private void releaseLock(String lockPath) {
try {
getZookeeperClient().delete(lockPath, -1);
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
}
private ZooKeeper zooKeeper = null;
/**
* 获取zookeeper客户端
* @return
* @throws Exception
*/
public ZooKeeper getZookeeperClient() throws Exception {
if(null == zooKeeper) {
lock.lock();
try {
if(null == zooKeeper) {
ZookeeperClient zookeeperClient = new ZookeeperClient();
zooKeeper = zookeeperClient.getZooKeeperClient(hosts);
}
} finally {
lock.unlock();
}
}
return zooKeeper;
}
}
4.Zookeeper分布式共享锁类测试方法
public class ZookeeperLockTest {
static class MsgConsumer extends Thread {
@Override
public void run() {
String hosts = "bigdata-master:2181,bigdata-slaver1:2181,bigdata-slaver2:2181";
ZookeeperLock zookeeperLock = ZookeeperLock.getInstance(hosts);
try {
String lockPath = zookeeperLock.lock();
while(true) {
Thread.sleep(1000);
break;
}
zookeeperLock.unlock(lockPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MsgConsumer consumer1 = new MsgConsumer();
MsgConsumer consumer2 = new MsgConsumer();
MsgConsumer consumer3 = new MsgConsumer();
MsgConsumer consumer4 = new MsgConsumer();
MsgConsumer consumer5 = new MsgConsumer();
MsgConsumer consumer6 = new MsgConsumer();
consumer1.start();
consumer2.start();
consumer3.start();
consumer4.start();
consumer5.start();
consumer6.start();
}
}