Curator是Apache ZooKeeper的Java / JVM客户端库,官网有个图很形象。
curator对于zookeeper来说就像Guava之余java.我们知道Guava是谷歌开源的java类库,该库经过高度优化,运用得当可极大提高我们的代码效率和质量。
所以,用Curator的前提是了解zookeeper,
在现在分布式应用大行其道的时代,分布式锁一直是热点问题。现在我们一起来看下curator是如果操作zookeeper,以及如何实现分布式锁的。
//1、制定重试策略:初试时间为1s 重试3次 默认值参考官网
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
//2、通过工厂创建连接
CuratorFramework client = CuratorFrameworkFactory.newClient(address, retryPolicy);
//3、开启连接
client.start();
//4 新建一个分布式锁对象,既然是在zookeeper中,当然要在节点中,这里节点已存在,不存在的自行创建
final InterProcessMutex mutex = new InterProcessMutex(client, "/testRoot");
//这是代码结构
if ( lock.acquire(maxWait, waitUnit) )
{
try
{
// do some work inside of the critical section here
}
finally
{
lock.release();
}
}
很明显,我们需要进入acquire方法
@Override
public boolean acquire(long time, TimeUnit unit) throws Exception
{
return internalLock(time, unit);
}
这个方法的作用就是获取锁,这方法还有两个参数,分别是等待的时间和单位。该方法会一直阻塞知道锁可用或者时间过期,并且该锁是可重入锁,每一个获取锁操作必须对应一个释放锁操作。
继续进入internalLock方法
private boolean internalLock(long time, TimeUnit unit) throws Exception
{
//取得当前线程
Thread currentThread = Thread.currentThread();
//取出与当前线程绑定的LockData对象
LockData lockData = threadData.get(currentThread);
//如果对象不为空,说明当前线程已经拥有锁
if ( lockData != null )
{
//每一次重入都原子计数加1
lockData.lockCount.incrementAndGet();
return true;
}
//获得锁的关键代码
String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
//第一次获得锁后,将当前线程和锁信息放入map中
if ( lockPath != null )
{
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}
return false;
}
继续看获取锁的关键代码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
{ //创建一个节点
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
//根据创建的节点来判断是否取得锁
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;
}
这段代码中核心的就是中间加了注释的那两行,一个是创建节点的createsTheLock方法,进入该方法
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;
}
用过java操作zookeeper的同学就能看出这就是对创建节点方法的封装,这里创建的是临时顺序节点。
再看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();
//得到当前节点名去掉父节点的前缀
String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
//这个方法判断当前线程创建的节点是否是最小的,因为之前按从小到大排序了,所以和第一元素比较就行啦
PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
//获得了锁
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();
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;
}
进入getsTheLock方法查看获取具体细节
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
{
//在已排序的节点中查找自己创建节点的下标
int ourIndex = children.indexOf(sequenceNodeName);
//验证ourIndex是否小于0 ,小于0表示该接节点不存在
validateOurIndex(sequenceNodeName, ourIndex);
//如果比maxLeases小, 则说明是最小的节点 maxLeases在创建锁InterProcessMutex示例时被初始化为1
boolean getsTheLock = ourIndex < maxLeases;
//如果不是最小的,则需要监听离你最近的节点
String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);
return new PredicateResults(pathToWatch, getsTheLock);
}
获取锁的过程基本就是这样,建议大家debug跟踪下。
思路其实和java操作zookeeper一样:
判断是否已经拥有锁,如果拥有技术加1直接返回
如果不是,则尝试创建节点,如果当前线程创建的节点值是最小的,则获取锁
如果不是最小,则监听利你最近的那个节点。
有获取锁就有释放锁,相对来说释放锁要简单些。
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);
}
//技术减一,由于锁是可重入的,所以如果newLockCount>0,则不能删除节点直接返回。
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
{ //从map移除当前线程的锁信息
threadData.remove(currentThread);
}
}