zookeeper实现分布式锁
上一章通过使用临时有序节点保证有序的资源竞争,今天我们就完整的实现一个使用zookeepr的分布式锁。
在多并发的情况下,在单机服务上,通过锁来保证在同一时间时,只有一个线程可以执行,用于保证临界资源的最终的结果准确性
那么在分布式服务时,要解决只有同一台机器执行一段有对临界资源同时访问的问题,就是通过分布式锁来保证其实现
实现分布式服务的框架有 Redis zookeeper 以及通过数据库表的形式来实现分布式锁
对于分布式锁 我们同认为需要和单机并发锁所具备的功能相似。
zookeeper有两种实现方式
- 通过对于临时有序节点的顺序监听实现
- 根据Zookeeper的开源客户端Curator实现分布式锁
现在就来实现一个比较简单一些的分布式锁吧。(通过对于临时有序节点的顺序监听实现)
import org.I0Itec.zkclient.ZkClient;
public abstract class AbstractLock {
//zk地址和端口
public static final String ZK_ADDR = "192.168.1.11:2181";
//超时时间
public static final int SESSION_TIMEOUT = 10000;
//创建zk
protected ZkClient zkClient = new ZkClient(ZK_ADDR, SESSION_TIMEOUT);
/**
* 可以认为是模板模式,两个子类分别实现它的抽象方法
* 1,简单的分布式锁
* 2,高性能分布式锁
*/
public void getLock() {
String threadName = Thread.currentThread().getName();
if (tryLock()) {
System.out.println(threadName+"-获取锁成功");
}else {
System.out.println(threadName+"-获取锁失败,进行等待...");
waitLock();
//递归重新获取锁
getLock();
}
}
public abstract void releaseLock();
public abstract boolean tryLock();
public abstract void waitLock();
}
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;
/**
* @author hongtaolong
* 简单的分布式锁的实现
*/
public class SimpleZkLock extends AbstractLock {
private static final String NODE_NAME = "/zk_lock";
private CountDownLatch countDownLatch;
@Override
public void releaseLock() {
if (null != zkClient) {
//删除节点
zkClient.delete(NODE_NAME);
zkClient.close();
System.out.println(Thread.currentThread().getName()+"-释放锁成功");
}
}
//直接创建临时节点,如果创建成功,则表示获取了锁,创建不成功则处理异常
@Override
public boolean tryLock() {
if (null == zkClient) return false;
try {
zkClient.createEphemeral(NODE_NAME);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public void waitLock() {
//监听器
IZkDataListener iZkDataListener = new IZkDataListener() {
//节点被删除回调
@Override
public void handleDataDeleted(String dataPath) throws Exception {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
//节点改变被回调
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
// TODO Auto-generated method stub
}
};
zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
//如果存在则阻塞
if (zkClient.exists(NODE_NAME)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 等待获取锁...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//删除监听
zkClient.unsubscribeDataChanges(NODE_NAME, iZkDataListener);
}
}
这里大家可以根据我上一个文章进行比较 zookeeper事件监听,其本质还是对于对于zookeeper在创建临时有序的节点时,会根据创建的顺序来保证获取锁的顺序,并在对应的节点上有相应的事件监听来保证获取最新的执行节点
下边时根据Zookeeper的开源客户端Curator实现分布式锁
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class CuratorDistributeLock {
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.11:2181",retryPolicy);
client.start();
CuratorFramework client2 = CuratorFrameworkFactory.newClient("192.168.1.11:2181",retryPolicy);
client2.start();
//创建分布式锁, 锁空间的根节点路径为/zk_lock/lock
InterProcessMutex mutex = new InterProcessMutex(client,"/zk_lock/lock");
final InterProcessMutex mutex2 = new InterProcessMutex(client2,"/zk_lock/lock");
try {
mutex.acquire();
} catch (Exception e) {
e.printStackTrace();
}
//获得了锁, 进行业务流程
System.out.println("clent Enter mutex");
Thread client2Th = new Thread(new Runnable() {
@Override
public void run() {
try {
//加锁方法 下边会有源码展示
mutex2.acquire();
System.out.println("client2 Enter mutex");
//释放锁
mutex2.release();
System.out.println("client2 release lock");
}catch (Exception e){
e.printStackTrace();
}
}
});
client2Th.start();
//完成业务流程, 释放锁
try {
Thread.sleep(5000);
mutex.release();
System.out.println("client release lock");
client2Th.join();
} catch (Exception e) {
e.printStackTrace();
}
//关闭客户端
client.close();
}
}
追看 acquire 方法会注意到以下方法
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
.....
while ( !isDone )
{
isDone = true;
try
{
//创建临时有序节点
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
//判断自己是否最小序号的节点,如果不是添加监听前面节点被删的通知
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
}
}
//如果获取锁返回节点路径
if ( hasTheLock )
{
return ourPath;
}
....
}
由此来看 开源框架的实现也是来自与对于 zookeeper 临时有序节点的封装。
参考文章:
https://www.jianshu.com/p/91976b27a188
https://www.pianshen.com/article/4687316303/