原文地址:http://blog.csdn.net/z69183787/article/details/79355829
zookeeper在项目中有很多应用,其中一个比较常见的的就是分布式锁,zookeeper实现分布式锁的原理是根据zookeeper创建的临时有序节点,每次zookeeper在同一个目录下创建的临时有序节点是有序的,会自动累加,如果本次操作创建的节点在目录内是最小节点,则获得锁,否则阻塞等待锁,并且总是在前一个节点上注册watcher监视前一个节点的释放,较小的节点释放后,等待的节点对应的操作获得锁,以此类推,代码简单实现如下:
public class ZKDisLock {
private static final Logger SERVICE_LOG = LoggerFactory.getLogger(LogConstants.SERVICE_LOG);
//锁目录
private static final String BASE_PATH = "/disLock" ;
private static final String SPLIT_FLAG = "_" ;
private Integer sessionOut;
private String zkHost;
private ZooKeeper zooKeeper;
//当前锁节点名称
private String lockNode;
//等待锁节点名称
private String waitNode;
private CountDownLatch countDownLatch;
public ZKDisLock(Integer sessionOut, String zkHost) {
this .sessionOut = sessionOut;
this .zkHost = zkHost;
}
//ZKDisLock初始化
public void init() throws DisLockException {
try {
zooKeeper = new ZooKeeper(zkHost, sessionOut, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
//判断有无根目录,没有的话创建
Stat stat = zooKeeper.exists(BASE_PATH, false );
if (stat == null ) {
zooKeeper.create(BASE_PATH, null , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (Exception e) {
SERVICE_LOG.info( "zk初始化失败" );
throw new DisLockException( "zk初始化失败" );
}
}
//加锁
public void lock(String content) throws DisLockException {
if (zooKeeper == null ) {
throw new DisLockException( "zk未初始化" );
}
try {
//创建节点
String itemName = zooKeeper.create(BASE_PATH + "/" + content + SPLIT_FLAG, null ,
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
//判断节点是否是目录中同一个content的最小节点
if (isLowestNode(content, itemName)) {
SERVICE_LOG.info( "线程:" + Thread.currentThread().getId() + ",加锁成功,节点名称:" + lockNode);
return ;
} else {
SERVICE_LOG.info( "线程:" + Thread.currentThread().getId() + ",等待锁:" + waitNode);
//不是最小节点,等待锁释放
waitForLock(sessionOut);
//锁释放后,再次判断下是否是最小节点
if (isLowestNode(content, itemName)) {
SERVICE_LOG.info( "线程:" + Thread.currentThread().getId() + ",获得锁,节点名称:" + lockNode);
return ;
}
}
} catch (Exception e) {
SERVICE_LOG.error( "加锁异常" );
throw new DisLockException( "加锁异常" );
}
}
//判断是否是最小节点
private boolean isLowestNode(String content, String itemName) throws Exception {
//获取所有子节点
List<String> nodeNames = zooKeeper.getChildren(BASE_PATH, null );
List<String> itemNodes = new ArrayList<>();
lockNode = itemName;
//过滤content节点
for (String name : nodeNames) {
if (name.split(SPLIT_FLAG)[ 0 ].toString().equals(content)) {
itemNodes.add(name);
}
}
//排序
Collections.sort(itemNodes);
//最小节点返回
if (itemName.equals(BASE_PATH + "/" + itemNodes.get( 0 ))) {
return true ;
}
//非最小节点,找到前一个节点
int nodeIndex = Collections.binarySearch(itemNodes, itemName.substring(itemName.lastIndexOf( "/" ) + 1 ));
waitNode = itemNodes.get(nodeIndex - 1 );
return false ;
}
//等待锁
private void waitForLock( final Integer waitTime) throws Exception {
//判断等待节点是否释放,同时注册watcher,通知释放事件
Stat stat = zooKeeper.exists(BASE_PATH + "/" + waitNode, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
SERVICE_LOG.info( "watch proceess " + watchedEvent.toString());
if (countDownLatch != null ) {
//锁释放,通知等待节点
countDownLatch.countDown();
}
}
});
//如果等待的锁不存在返回
if (stat == null ) {
return ;
}
//存在,等待
countDownLatch = new CountDownLatch( 1 );
countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
}
//释放锁
public void unLock() throws DisLockException {
try {
//删除锁节点
zooKeeper.delete(lockNode, - 1 );
SERVICE_LOG.info( "线程:" + Thread.currentThread().getId() + ",解锁成功,节点名称:" + lockNode);
lockNode = null ;
zooKeeper.close();
} catch (Exception e) {
String errorMsg = "解锁异常,节点名称:" + lockNode;
SERVICE_LOG.error(errorMsg);
throw new DisLockException(errorMsg);
}
}
}
|
以上是用zookeeper实现分布式锁的一个简单实现,可能有些地方并不太严谨,但是足以说明问题,下面来测试下这个分布式锁:
public static void main(String[] args) throws Exception{
ExecutorService executorService= Executors.newFixedThreadPool( 40 );
String content= "abcdefg" ;
String zkHost= "127.0.0.1" ;
Integer sessionOut= 30000 ;
//创建10个线程测试
for ( int i= 0 ;i< 10 ;++i){
ZKDisLock disLock= new ZKDisLock(sessionOut,zkHost);
disLock.init();
executorService.submit( new TestJob(disLock,content));
}
}
//测试任务
private static class TestJob implements Runnable{
private ZKDisLock disLock;
private String content;
public TestJob(ZKDisLock disLock, String content) {
this .disLock = disLock;
this .content = content;
}
@Override
public void run() {
try {
disLock.lock(content);
Thread.sleep( 2000 );
disLock.unLock();
} catch (Exception e){
System.out.println(e.getMessage());
}
}
}
|
运行结果如下:
17 - 12 - 20 15 : 03 : 03 , 436 INFO service(ZKDisLock.java: 73 ) ## 线程: 13 ,等待锁:abcdefg_0000000327
17 - 12 - 20 15 : 03 : 03 , 436 INFO service(ZKDisLock.java: 73 ) ## 线程: 19 ,等待锁:abcdefg_0000000328
17 - 12 - 20 15 : 03 : 03 , 436 INFO service(ZKDisLock.java: 70 ) ## 线程: 16 ,加锁成功,节点名称:/disLock/abcdefg_0000000327
17 - 12 - 20 15 : 03 : 03 , 438 INFO service(ZKDisLock.java: 73 ) ## 线程: 22 ,等待锁:abcdefg_0000000329
17 - 12 - 20 15 : 03 : 03 , 441 INFO service(ZKDisLock.java: 73 ) ## 线程: 25 ,等待锁:abcdefg_0000000330
17 - 12 - 20 15 : 03 : 03 , 444 INFO service(ZKDisLock.java: 73 ) ## 线程: 28 ,等待锁:abcdefg_0000000331
17 - 12 - 20 15 : 03 : 03 , 456 INFO service(ZKDisLock.java: 73 ) ## 线程: 31 ,等待锁:abcdefg_0000000332
17 - 12 - 20 15 : 03 : 03 , 460 INFO service(ZKDisLock.java: 73 ) ## 线程: 34 ,等待锁:abcdefg_0000000333
17 - 12 - 20 15 : 03 : 03 , 464 INFO service(ZKDisLock.java: 73 ) ## 线程: 37 ,等待锁:abcdefg_0000000334
17 - 12 - 20 15 : 03 : 03 , 469 INFO service(ZKDisLock.java: 73 ) ## 线程: 40 ,等待锁:abcdefg_0000000335
17 - 12 - 20 15 : 03 : 05 , 442 INFO service(ZKDisLock.java: 137 ) ## 线程: 16 ,解锁成功,节点名称:/disLock/abcdefg_0000000327
17 - 12 - 20 15 : 03 : 05 , 443 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000327
17 - 12 - 20 15 : 03 : 05 , 444 INFO service(ZKDisLock.java: 76 ) ## 线程: 13 ,获得锁,节点名称:/disLock/abcdefg_0000000328
17 - 12 - 20 15 : 03 : 07 , 449 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000328
17 - 12 - 20 15 : 03 : 07 , 449 INFO service(ZKDisLock.java: 137 ) ## 线程: 13 ,解锁成功,节点名称:/disLock/abcdefg_0000000328
17 - 12 - 20 15 : 03 : 07 , 451 INFO service(ZKDisLock.java: 76 ) ## 线程: 19 ,获得锁,节点名称:/disLock/abcdefg_0000000329
17 - 12 - 20 15 : 03 : 09 , 459 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000329
17 - 12 - 20 15 : 03 : 09 , 459 INFO service(ZKDisLock.java: 137 ) ## 线程: 19 ,解锁成功,节点名称:/disLock/abcdefg_0000000329
17 - 12 - 20 15 : 03 : 09 , 461 INFO service(ZKDisLock.java: 76 ) ## 线程: 22 ,获得锁,节点名称:/disLock/abcdefg_0000000330
17 - 12 - 20 15 : 03 : 11 , 469 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000330
17 - 12 - 20 15 : 03 : 11 , 469 INFO service(ZKDisLock.java: 137 ) ## 线程: 22 ,解锁成功,节点名称:/disLock/abcdefg_0000000330
17 - 12 - 20 15 : 03 : 11 , 471 INFO service(ZKDisLock.java: 76 ) ## 线程: 25 ,获得锁,节点名称:/disLock/abcdefg_0000000331
17 - 12 - 20 15 : 03 : 13 , 480 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000331
17 - 12 - 20 15 : 03 : 13 , 480 INFO service(ZKDisLock.java: 137 ) ## 线程: 25 ,解锁成功,节点名称:/disLock/abcdefg_0000000331
17 - 12 - 20 15 : 03 : 13 , 482 INFO service(ZKDisLock.java: 76 ) ## 线程: 28 ,获得锁,节点名称:/disLock/abcdefg_0000000332
17 - 12 - 20 15 : 03 : 15 , 488 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000332
17 - 12 - 20 15 : 03 : 15 , 488 INFO service(ZKDisLock.java: 137 ) ## 线程: 28 ,解锁成功,节点名称:/disLock/abcdefg_0000000332
17 - 12 - 20 15 : 03 : 15 , 490 INFO service(ZKDisLock.java: 76 ) ## 线程: 31 ,获得锁,节点名称:/disLock/abcdefg_0000000333
17 - 12 - 20 15 : 03 : 17 , 498 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000333
17 - 12 - 20 15 : 03 : 17 , 498 INFO service(ZKDisLock.java: 137 ) ## 线程: 31 ,解锁成功,节点名称:/disLock/abcdefg_0000000333
17 - 12 - 20 15 : 03 : 17 , 500 INFO service(ZKDisLock.java: 76 ) ## 线程: 34 ,获得锁,节点名称:/disLock/abcdefg_0000000334
17 - 12 - 20 15 : 03 : 19 , 506 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000334
17 - 12 - 20 15 : 03 : 19 , 506 INFO service(ZKDisLock.java: 137 ) ## 线程: 34 ,解锁成功,节点名称:/disLock/abcdefg_0000000334
17 - 12 - 20 15 : 03 : 19 , 508 INFO service(ZKDisLock.java: 76 ) ## 线程: 37 ,获得锁,节点名称:/disLock/abcdefg_0000000335
17 - 12 - 20 15 : 03 : 21 , 516 INFO service(ZKDisLock.java: 118 ) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted
path:/disLock/abcdefg_0000000335
17 - 12 - 20 15 : 03 : 21 , 516 INFO service(ZKDisLock.java: 137 ) ## 线程: 37 ,解锁成功,节点名称:/disLock/abcdefg_0000000335
17 - 12 - 20 15 : 03 : 21 , 518 INFO service(ZKDisLock.java: 76 ) ## 线程: 40 ,获得锁,节点名称:/disLock/abcdefg_0000000336
17 - 12 - 20 15 : 03 : 23 , 527 INFO service(ZKDisLock.java: 137 ) ## 线程: 40 ,解锁成功,节点名称:/disLock/abcdefg_0000000336
|
从以上结果可以看到,线程16最先获得锁,创建最小节点 /disLock/abcdefg_0000000327,其次是线程13等待线程16的锁释放,其对应节点是/disLock/abcdefg_0000000328,等待abcdefg_0000000327释放,线程16锁释放后,线程13获得锁,其次是线程19等在线程13之后,以此类推,最后所有线程都获得了锁并且成功解锁,另外上述运行结果也展示了线程之间的锁的交接时通过注册的watcher传递的。以上的测试结果表明,我们实现的分布式锁可以正确的运行,达到了预期效果。