上篇文章讲到了如何利用zookeeper实现分布式锁,并提供了代码示例。但是前面的解决方案存在如下缺点:
1、客户端接受了很多与自己无关的事件通知。因为一个进程解锁后会通知所有后面正在监听的进程,这是没有必要的;
2、由于上面的过多的事件监听和通知,导致并发量大时,性能较差。
典型的羊群效应:
1、巨大的服务器性能损耗;
2、网络冲击;
3、可能造成宕机。
一、改进后的数据结构和流程
改进后的方案使用临时顺序节点,具体流程如下:
改进后的实现流程二、实现代码示例
public class ZookeeperImproveLock extends AbstractLock{
//当前请求的节点
private String currentPath ;
//当前请求节点的前一个节点
private String beforePath ;
private CountDownLatch cdl =null;
/**
* 解锁即删除当前节点
*/
@Override
public void unLock() {
zkClient.delete(currentPath);
currentPath =null;
}
/**
* 如果创建临时节点成表示获取锁成功
*/
@Override
protected boolean tryLock() {
try{
//如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
if(currentPath==null||currentPath.length()<=0){
//创建一个临时顺序节点
currentPath = zkClient.createEphemeralSequential(path, "lock");
}
//获取所有临时顺序节点并排序,临时节点为自增长的字符串,如00001;
List<String> children = zkClient.getChildren(path);
//进行排序
Collections.sort(children);
if(currentPath.equals(path+children.get(0))){
//如果当前节点在所有节点中排名第一则获取成功
return true;
}else{
//如果当前节点在所有节点中排名不是第一,则获取当前节点的前面的节点名称并赋值
int wz = Collections.binarySearch(children, currentPath.substring(5));
beforePath = path + children.get(wz -1);
return false;
}
}catch(Exception e){
return false ;
}
}
@Override
protected void waitForLock() {
IZkDataListener listener = new IZkDataListener(){
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
//监听临时节点删除事件
if(cdl!=null){
cdl.countDown();
}
}
};
//订阅节点改变事件
zkClient.subscribeDataChanges(beforePath, listener);
if(zkClient.exists(beforePath)){
cdl = new CountDownLatch(1);
try {
cdl.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(beforePath, listener);
}
}
网友评论