Curator API提供了基于Zookeeper的分布式锁的实现
通过查看
InterProcessMutex
和LockInternals
源码,确定分布式锁的锁定和释放流程
互斥锁设计的核心思想:同一时间,仅一个进程/线程可以占有
- 临时节点:利用临时节点,会话中断,就会删除的特点,避免死锁
- 节点的顺序性:利用同一路径下,不能存在相同节点,节点创建存在顺序,先创建的节点的序号更小,序号最小的节点占有锁
- Watch机制:监听当前占用锁的路径,如果锁对应的路径被修改,就唤醒所有等待的节点
获取锁
// InterProcessMutex
public void acquire() throws Exception
{
// 获取锁
if ( !internalLock(-1, null) )
{
throw new IOException("Lost connection while trying to acquire lock: " + basePath);
}
}
private boolean internalLock(long time, TimeUnit unit) throws Exception
{
/*
Note on concurrency: a given lockData instance
can be only acted on by a single thread so locking isn't necessary
*/
Thread currentThread = Thread.currentThread();
LockData lockData = threadData.get(currentThread);
//线程已经占用锁,增加重入次数
if ( lockData != null )
{
// re-entering
lockData.lockCount.incrementAndGet();
return true;
}
// 尝试获取锁
String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
//设置
if ( lockPath != null )
{
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}
return false;
}
// LockInternals
// 该Watcher对象,一旦收到通知,就会唤醒所有阻塞的线程
private final Watcher watcher = new Watcher()
{
@Override
public void process(WatchedEvent event)
{
client.postSafeNotify(LockInternals.this);
}
};
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
final long startMillis = System.currentTimeMillis();
final Long millisToWait = (unit != null) ? unit.toMillis(time) : null;
final byte[] localLockNodeBytes = (revocable.get() != null) ? new byte[0] : lockNodeBytes;
int retryCount = 0;
String ourPath = null;
boolean hasTheLock = false;
boolean isDone = false;
while ( !isDone )
{
isDone = true;
try
{
// 在path下创建一个路径,作为锁路径
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
// 循环获取锁,成功获取锁,返回true;否则,返回false
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
}
catch ( KeeperException.NoNodeException e )
{
// 会话超时导致的锁释放,重试获取锁
if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
{
isDone = false;
}
else
{
// 超过最大重试次数,抛出异常
throw e;
}
}
}
if ( hasTheLock )
{
// 返回锁路径
return ourPath;
}
return null;
}
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception
{
boolean haveTheLock = false;
boolean doDelete = false;
try
{
if ( revocable.get() != null )
{
client.getData().usingWatcher(revocableWatcher).forPath(ourPath);
}
// 需要在和Zookeeper保持连接,且未获取到锁,就不断循环获取
while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )
{
List<String> children = getSortedChildren();
String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
// 获取锁
PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
// 成功获取锁,退出循环,返回true
if ( predicateResults.getsTheLock() )
{
haveTheLock = true;
}
else
{
// 获取需要监听的节点的绝对路径
String previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
synchronized(this)
{
try
{
// use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
//监听对应的路径
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
// 设置了超时时间
if ( millisToWait != null )
{
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
// 已经超时,将doDelete标志位设置为true,后续删除节点
if ( millisToWait <= 0 )
{
doDelete = true; // timed out - delete our node
break;
}
// 未超时,同步阻塞当前线程millisToWait时间
wait(millisToWait);
}
else
{
// 未设置超时时间,直接同步阻塞当前线程
wait();
}
}
catch ( KeeperException.NoNodeException e )
{
// it has been deleted (i.e. lock released). Try to acquire again
}
}
}
}
}
catch ( Exception e )
{
ThreadUtils.checkInterrupted(e);
doDelete = true;
throw e;
}
finally
{
// 设置doDelete=true,删除路径
if ( doDelete )
{
deleteOurPath(ourPath);
}
}
return haveTheLock;
}
// 获取锁
// maxLeases默认为1,表示资源数
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
{
// 在有序的列表中,找到当前节点的索引,索引从0开始,如果<0,表示没找到当前节点
int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);
// 当前路径所属索引为0时,才能成功获取锁
boolean getsTheLock = ourIndex < maxLeases;
// 需要监听当前节点的前一个节点(这里因为maxLeases为1)
String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);
return new PredicateResults(pathToWatch, getsTheLock);
}
释放锁
释放锁的过程较为简单。
参考
- https://juejin.cn/post/6844903873296105479
- https://juejin.cn/post/6844903608685707271
- Zookeeper实现分布式锁: