文章目錄
導讀
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.)
}
}