背景
本文章基于zookeeper实现分布式锁,如果有读者想了解基于redis实现分布式锁,可以看我上篇文章
关于分布式锁产生的背景以及相关概念,这里不做过多解释,可以网上了解。需要读者对zookeeper有一定的了解。
zookeeper
ZooKeeper是一个[分布式]的,开放源码的[分布式应用程序]协调服务,是[Google]的Chubby一个[开源]的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。关于过半选举等这里不做介绍。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
抓住重点
- zookeeper是一个
分布式
服务组件,在分布式场景下首先需要解决的问题就是在并发情况保证数据一致性,不用担心,zookeeper已经帮我们实现了 - 类似文件系统,存储形式与我们在window系统中文件夹、文件相似
在zookeeper中保存结构如下图
紫色节点称为node,有4种类型的znode
1、PERSISTENT–持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
2、PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
3、EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
核心实现
类似文件系统,在同一个目录下,有相同文件名的文件,即不允许存在相同目录下存在重复znode,实现分布式锁也是基于这一点。
zk客户端
zookeeper官方已经提供了两种JAVA实现的客户端(zkClient、Curator)操作zookeeper,用户引入其中一个就可以,本次使用curator
<!-- zookeeper -->
<!-- 对zookeeper的底层api的一些封装 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 提供一些客户端的操作,例如重试策略等 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
加锁
加锁使用zookeeper临时(EPHEMERAL)节点,zookeeper与应用建立长连接,当连接断开后在一定时间内没有重连,则节点自动消除,主要是为了防止死锁
,比如某个应用加锁后,由于宕机等情况没解锁,锁一直存在,其他节点无法执行任务。
1、创建加锁目录,持久节点
private static final String ZK_LOCK_PATH = "/lock/zkLock";
private static final String LOCK = ".lock";
public ZkLock() {
ZkUtils.createPersistNode(ZK_LOCK_PATH);
}
2、创建临时加锁节点
public static boolean createTempNode(String path, String node, String value) {
boolean success;
try {
zkClient.create().withMode(CreateMode.EPHEMERAL).forPath(mkPath(path, node), value.getBytes());
success = true;
} catch (Exception e) {
String errorMsg = Optional.ofNullable(e.getMessage()).orElse(e.toString());
log.error("Failed to lock [" + node + "] ::" + errorMsg);
success = false;
}
return success;
}
@Override
public void lock() {
if (tryLock()) {
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
lock();
}
@Override
public boolean tryLock() {
String salerName = Thread.currentThread().getName();
boolean success = ZkUtils.createTempNode(ZK_LOCK_PATH, LOCK,salerName);
if (success) {
return true;
}
return false;
}
解锁
解锁即删除znode
@Override
public void unlock() {
ZkUtils.removeNode(ZK_LOCK_PATH, LOCK);
}
场景验证
还是以上篇文章中菜鸟带你手撕分布式锁-redis,看完不会算我输的售票作为验证例子,使用zkLock
private Lock lock = new ZkLock();
private int tickets = 100;
public void run() {
for (; ; ) {
if (tickets <= 0) break;
lock.lock();
try {
sale();
} finally {
lock.unlock();
}
}
}
通过zk工具查看发现已经开始加锁,当前是线程2加锁
继续观察,加锁对象变了,当前是线程3加锁
程序执行完成后结果
最后一个执行完,查看是否释放锁,发下加锁的znode已经没有了,锁释放了
结束语
幸福来的太突然,结束了o( ̄︶ ̄)o ,完整代码已上传github,链接在上篇文章结尾