Zookeeper实现分布式锁总结
优点:
- 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题
- 实现较为简单
缺点:
- 性能上不如使用缓存实现的分布式锁,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能
- 需要对Zookeeper的原理有所了解
排他锁
排他锁,又称写锁或独占锁。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T1对O1进行读取或更新操作,其他任务事务都不能对这个数据对象进行任何操作,直到T1释放了排他锁。
排他锁核心是保证当前有且仅有一个事务获得锁,并且锁释放之后,所有正在等待获取锁的事务都能够被通知到。
Zookeeper 的强一致性特性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即Zookeeper将会保证客户端无法重复创建一个已经存在的数据节点。可以利用Zookeeper这个特性,实现排他锁。
- 定义锁:通过Zookeeper上的数据节点来表示一个锁
-
获取锁:客户端通过调用
create
方法创建表示锁的临时节点,可以认为创建成功的客户端获得了锁,同时可以让没有获得锁的节点在该节点上注册Watcher监听,以便实时监听到lock节点的变更情况 -
释放锁:以下两种情况都可以让锁释放
- 当前获得锁的客户端发生宕机或异常,那么Zookeeper上这个临时节点就会被删除
- 正常执行完业务逻辑,客户端主动删除自己创建的临时节点
基于Zookeeper实现排他锁流程:
代码实现如下:
package zk.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
/**
* Created by Joe on 2019/3/6.
*/
public class ExclusiveLock {
private static final Logger logger = LoggerFactory.getLogger(ExclusiveLock.class);
private static final String LOCK_NAMESPACE = "lock_space";
private static final String LOCK_NODE = "exclusive_lock";
// 用于挂起请求,并等待上一个分布式锁的释放
private static CountDownLatch countDownLatch;
private CuratorFramework client;
private String name;
public ExclusiveLock(String zkPath, String name) {
this.name = name;
// zk 客户端初始化
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.connectString(zkPath)
.sessionTimeoutMs(10000)
.retryPolicy(retryPolicy)
.namespace(LOCK_NAMESPACE)
.build();
client.start();
// countDownLatch 初始化
countDownLatch = new CountDownLatch(1);
init();
}
private void init() {
try {
if (Objects.isNull(client.checkExists().forPath("/"))) {
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.forPath("/");
}
addWatcherToLock("/");
} catch (Exception e) {
e.printStackTrace();
}
}
public void getLock() {
// 使用死循环用于获取请求
while (true) {
try {
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.forPath("/" + LOCK_NODE);
logger.info("{} 分布式锁获取成功", name);
return;
} catch (Exception e) {
e.printStackTrace();
logger.info("{} 分布式锁获取失败", name);
try {
synchronized (ExclusiveLock.class) {
if (countDownLatch.getCount() <= 0) {
countDownLatch = new CountDownLatch(1);
}
}
countDownLatch.await();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
public boolean releaseLock() {
try {
if (Objects.nonNull(client.checkExists().forPath("/" + LOCK_NODE))) {
client.delete().forPath("/" + LOCK_NODE);
}
logger.info("{} 分布式锁释放成功", name);
} catch (Exception e) {
logger.info("{} 分布式锁释放失败", name);
return false;
}
return true;
}
private void addWatcherToLock(String path) throws Exception {
PathChildrenCache cache = new PathChildrenCache(client, path, true);
cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new ExclusiveLockWatcherListener());
}
private static class ExclusiveLockWatcherListener implements PathChildrenCacheListener {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// 监听子节点被移除事件
if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(event.getType())) {
String path = event.getData().getPath();
logger.info("上一个会话已经释放锁 or 会话已经端盖,节点路径为 {}", path);
if (path.contains(LOCK_NODE)) {
System.out.println("释放计数器,让其他请求来获得分布式锁");
countDownLatch.countDown();
}
}
}
}
}
测试代码:
package zk.curator;
/**
* Created by Joe on 2019/3/6.
*/
public class Test {
private static final String zkServerPath = "ip:port";
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
final int index = i;
threads[i] = new Thread(() -> {
ExclusiveLock exclusiveLock = new ExclusiveLock(zkServerPath, "thread" + index);
exclusiveLock.getLock();
exclusiveLock.releaseLock();
});
threads[i].start();
}
}
}