一.Curator简介
Curator 是 Netflix 公司开源的一个 Zookeeper 客户端 ,Curator 框架在 Zookeeper 原生 API 接口上进行了包装,解决了很多 zooKeeper 客户端非常底层的细节开发。提供 ZooKeeper 各种应用场景,如:分布式锁、集群领导选举、共享计数器、缓存机制、分布式队列等的抽象封装,是最好用,最流行的 Zookeeper 的客户端。
下面将使用 Curator 客户端来实现 Java 版的 Zookeeper 分布式锁。
二.Spring Boot 整合 Curator
1.配置文件
zookeeper:
curator:
# 集群连接信息
ip: 192.168.3.101:2181,192.168.3.102:2181,192.168.3.103:2181
# 会话超时时长
sessionTimeOut: 60000
# 重试间隔时长
sleepMsBetweenRetries: 1000
# 重试次数
maxRetries: 3
# 连接超时时长
connectionTimeoutMs: 5000
2.Curator的配置类
package com.learn.blog.demo;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "zookeeper.curator")
public class CuratorConfig {
private String ip;
private int sessionTimeOut;
private int sleepMsBetweenRetries;
private int maxRetries;
private int connectionTimeoutMs;
@Bean("curatorFramework")
public CuratorFramework curatorClient() {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString(ip)
.sessionTimeoutMs(sessionTimeOut)
.connectionTimeoutMs(connectionTimeoutMs)
.retryPolicy(new RetryNTimes(maxRetries, sleepMsBetweenRetries))
.build();
curatorFramework.start();
return curatorFramework;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getSessionTimeOut() {
return sessionTimeOut;
}
public void setSessionTimeOut(int sessionTimeOut) {
this.sessionTimeOut = sessionTimeOut;
}
public int getSleepMsBetweenRetries() {
return sleepMsBetweenRetries;
}
public void setSleepMsBetweenRetries(int sleepMsBetweenRetries) {
this.sleepMsBetweenRetries = sleepMsBetweenRetries;
}
public int getMaxRetries() {
return maxRetries;
}
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getConnectionTimeoutMs() {
return connectionTimeoutMs;
}
public void setConnectionTimeoutMs(int connectionTimeoutMs) {
this.connectionTimeoutMs = connectionTimeoutMs;
}
}
三.分布式锁的实现
Curator 的 acquire方法和 release 方法的工作流程讲解
acquire方法是 Curator 客户端中获取分布式锁
release方法是 Curator 客户端中释放分布式锁
源码分析
acquire方法的源码解析
public void acquire() throws Exception {
if (!this.internalLock(-1L, (TimeUnit)null)) {
throw new IOException("Lost connection while trying to acquire lock: " + this.basePath);
}
}
private boolean internalLock(long time, TimeUnit unit) throws Exception {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 从 ConcurrentMap 中获取当前线程的 lockData
InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
// 如果 lockData 不为null,说明该线程之前已经获取到了锁
if (lockData != null) {
// 可重入数量加一
lockData.lockCount.incrementAndGet();
// 返回加锁成功
return true;
} else {
// 向 zookeeper 添加节点
String lockPath = this.internals.attemptLock(time, unit, this.getLockNodeBytes());
// 如果添加成功
if (lockPath != null) {
InterProcessMutex.LockData newLockData = new InterProcessMutex.LockData(currentThread, lockPath);
// 把当前线程的 lockData 存入 ConcurrentMap
this.threadData.put(currentThread, newLockData);
return true;
} else {
return false;
}
}
// ConcurrentMap 中 key 是 Thread,value 是 LockData
private static class LockData {
// 线程
Thread owningThread;
// 锁的路径
final String lockPath;
// 锁的重入数
final AtomicInteger lockCount;
private LockData(Thread owningThread, String lockPath) {
this.lockCount = new AtomicInteger(1);
this.owningThread = owningThread;
this.lockPath = lockPath;
}
}
release方法的源码解析
public void release() throws Exception {
// 获取当前的线程
Thread currentThread = Thread.currentThread();
// 从 ConcurrentMap 中获取当前线程的 lockData
InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
// 如果 lockData 为 null,说明该线程释放的锁不是自己的锁,抛出异常
if (lockData == null) {
throw new IllegalMonitorStateException("You do not own the lock: " + this.basePath);
} else {
// 判断可重入的数量
int newLockCount = lockData.lockCount.decrementAndGet();
if (newLockCount <= 0) {
// 小于 0 直接抛出异常
if (newLockCount < 0) {
throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + this.basePath);
} else {
try {
// 等于 0,删除 zookeeper 上的节点
this.internals.releaseLock(lockData.lockPath);
} finally {
// 移除该线程在 ConcurrentMap 的记录
this.threadData.remove(currentThread);
}
}
}
}
}
分布式锁代码实现
package com.learn.blog.demo;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
public class ZKLock {
@Autowired
private CuratorFramework curatorFramework;
private InterProcessMutex interProcessMutex;
public ZKLock() {
interProcessMutex = new InterProcessMutex(curatorFramework, "/lock");
}
public void lock() {
try {
interProcessMutex.acquire();
} catch (Exception e) {
e.printStackTrace();
}
}
public void release() {
try {
interProcessMutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}