zookeeper应用之分布式锁

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传递的。以上的测试结果表明,我们实现的分布式锁可以正确的运行,达到了预期效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值