文章目录
导读
zookeeper实现分布式锁原理就是:
因为zk节点不可重名,所以一次只有一个客户端会写入成功,同时利用临时节点,当客户端断开连接时候,删除临时节点。但是这有一个问题就是,当有很多线程争抢锁时候,拥有着释放锁,其余的会一起哄拥而上,就是惊群效应,带来的问题就是浪费zk性能。
所以利用临时有序节点的特性,节点只需watch上一个临时节点,上个节点释放锁删除时候,只通知后一个节点,这就解决了问题,同时这是个公平锁。
1 :获取锁:acquire()
构造方法
public InterProcessMutex(CuratorFramework client, String path)
{
this(client, path, new StandardLockInternalsDriver());
}
public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
{
this(client, path, LOCK_NAME, 1, driver);
}
InterProcessMutex(CuratorFramework client, String path, String lockName, int maxLeases, LockInternalsDriver driver)
{
basePath = PathUtils.validatePath(path);
internals = new LockInternals(client, driver, path, lockName, maxLeases);
}
获取锁
/**
* Acquire the mutex - blocking until it's available. Note: the same thread
* can call acquire re-entrantly. Each call to acquire must be balanced by a call
* to {@link #release()}
* 会一直阻塞去获取锁,除非连接断了异常。这是可重入锁,必须释放锁
* @throws Exception ZK errors, connection interruptions
*/
@Override
public void acquire() throws Exception{
if ( !internalLock(-1, null) ){
throw new IOException("Lost connection while trying to acquire lock: " + basePath);
}
}
//线程和锁的映射
private final ConcurrentMap<Thread, LockData> threadData = Maps.newConcurrentMap();
private boolean internalLock(long time, TimeUnit unit) throws Exception {
Thread currentThread = Thread.currentThread();
LockData lockData = threadData.get(currentThread);
if ( lockData != null )
{
// re-entering 表示重入,计数+1
lockData.lockCount.incrementAndGet();
return true;
}
String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
if ( lockPath != null ){
//获取到锁则将线程和锁放入到chm中去
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}
return false;
}
接下来看关键的LockInternals#attemptLock()方法:
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
{
//在zk上创建临时有序节点,并返回path
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
//循环等待来获取锁
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
}
catch ( KeeperException.NoNodeException e )
{
// gets thrown by StandardLockInternalsDriver when it can't find the lock node
// this can happen when the session expires, etc. So, if the retry allows, just try it all again
if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
{
isDone = false;
}
else
{
throw e;
}
}
}
if ( hasTheLock ) {
return ourPath;
}
return null;
}
StandardLockInternalsDriver#createTheLock()
@Override
public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception
{
String ourPath;
if ( lockNodeBytes != null )
{
ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);
}
else
{
//创建有序临时节点
ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);
}
return ourPath;
}
CreateMode.EPHEMERAL_SEQUENTIAL: 有序临时节点
这个方法就是根据是否传入lockNodeBytes数组,如果没有则使用ip作为默认的path name。创建有序临时节点,并返回创建节点的path。
LockInternals#internalLockLoop()
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);
}
while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )
{
//获取排序后的子节点集合(临时有序节点)
List<String> children = getSortedChildren();
//获取当前节点的排序(大致节点这样,记不清了:dhfdklgfdkgjdgf8df415e54545-lock-0000005 这里0000005就是序号)
String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
//如果没有获取到锁,则返回false和上一个临时节点的path
PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
if ( predicateResults.getsTheLock() ){
//获取到锁
haveTheLock = true;
}else{
//上一个节点的path,并watch
String previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
synchronized(this)
//因为wait()需要synchroniezed配合
{
try
{
//watch上个临时节点,监听到上个节点删除,则获取到锁,这里是公平锁
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
if ( millisToWait != null )
{
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if ( millisToWait <= 0 )
{
doDelete = true; // timed out - delete our node
break;
}
//超时等待
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
{
if ( doDelete )
{
deleteOurPath(ourPath);
}
}
return haveTheLock;
}
StandardLockInternalsDriver#getsTheLock
@Override
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
{
//当前临时节点在排序后的下标
int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);
//这里maxLease,独占锁=1,如果下标小于1则获取到锁了
boolean getsTheLock = ourIndex < maxLeases;
//如果没有获取到锁,则watch他的上一个临时节点
String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);
//构造返回结果
return new PredicateResults(pathToWatch, getsTheLock);
}
2 释放锁:release()
比较简单就不说了
@Override
public void release() 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 )
{
throw new IllegalMonitorStateException("You do not own the lock: " + basePath);
}
int newLockCount = lockData.lockCount.decrementAndGet();
if ( newLockCount > 0 )
{
return;
}
if ( newLockCount < 0 )
{
throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + basePath);
}
try
{
internals.releaseLock(lockData.lockPath);
}
finally
{
threadData.remove(currentThread);
}
}
final void releaseLock(String lockPath) throws Exception
{
client.removeWatchers();
revocable.set(null);
deleteOurPath(lockPath);
}
private void deleteOurPath(String ourPath) throws Exception
{
try
{
client.delete().guaranteed().forPath(ourPath);
}
catch ( KeeperException.NoNodeException e )
{
// ignore - already deleted (possibly expired session, etc.)
}
}