使用zookeeper实现分布式共享锁

这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watcher机制,来简单实现分布式锁。

主要思想:

1、开启10个线程,在disLocks节点下各自创建名为sub的EPHEMERAL_SEQUENTIAL节点;

2、获取disLocks节点下所有子节点,排序,如果自己的节点编号最小,则获取锁;

3、否则watch排在自己前面的节点,监听到其删除后,进入第2步(重新检测排序是防止监听的节点发生连接失效,导致的节点删除情况);

4、删除自身sub节点,释放连接;

 

这里插播下zookeeper的4种节点类型:

 

 

  1. public enum CreateMode {  

  2.      

  3.     /** 

  4.      * 持久节点:节点创建后,会一直存在,不会因客户端会话失效而删除; 

  5.      */  

  6.     PERSISTENT (0falsefalse),  

  7.   

  8.     /** 

  9.     * 持久顺序节点:基本特性与持久节点一致,创建节点的过程中,zookeeper会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名;  

  10.     */  

  11.     PERSISTENT_SEQUENTIAL (2falsetrue),  

  12.   

  13.     /** 

  14.      *  临时节点:客户端会话失效或连接关闭后,该节点会被自动删除,且不能再临时节点下面创建子节点,否则报如下错:org.apache.zookeeper.KeeperException$NoChildrenForEphemeralsException; 

  15.      */  

  16.     EPHEMERAL (1truefalse),  

  17.   

  18.     /** 

  19.      * 临时顺序节点:基本特性与临时节点一致,创建节点的过程中,zookeeper会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名;  

  20.      */  

  21.     EPHEMERAL_SEQUENTIAL (3truetrue);  

  22.     private static final Logger LOG = LoggerFactory.getLogger(CreateMode.class);  

  23.     private boolean ephemeral;  

  24.     private boolean sequential;  

  25.     private int flag;  

  26.     CreateMode(int flag, boolean ephemeral, boolean sequential) {  

  27.         this.flag = flag;  

  28.         this.ephemeral = ephemeral;  

  29.         this.sequential = sequential;  

  30.     }  

  31.     public boolean isEphemeral() {  

  32.         return ephemeral;  

  33.     }  

  34.     public boolean isSequential() {  

  35.         return sequential;  

  36.     }  

  37.     public int toFlag() {  

  38.         return flag;  

  39.     }  

  40.     static public CreateMode fromFlag(int flag) throws KeeperException {  

  41.         switch(flag) {  

  42.         case 0return CreateMode.PERSISTENT;  

  43.         case 1return CreateMode.EPHEMERAL;  

  44.         case 2return CreateMode.PERSISTENT_SEQUENTIAL;  

  45.         case 3return CreateMode.EPHEMERAL_SEQUENTIAL ;  

  46.         default:  

  47.             LOG.error("Received an invalid flag value to convert to a CreateMode");  

  48.             throw new KeeperException.BadArgumentsException();  

  49.         }  

  50.     }  

  51. }  


 

测试代码:

 

  1. package zookeeper;  

  2. import org.slf4j.Logger;  

  3. import org.slf4j.LoggerFactory;  

  4. import org.apache.zookeeper.*;  

  5. import org.apache.zookeeper.data.Stat;  

  6. import java.util.List;  

  7. import java.io.IOException;  

  8. import java.util.Collections;  

  9. import java.util.concurrent.CountDownLatch;  

  10.   

  11. public class DistributedLock implements Watcher{  

  12.     private int threadId;  

  13.     private ZooKeeper zk = null;  

  14.     private String selfPath;  

  15.     private String waitPath;  

  16.     private String LOG_PREFIX_OF_THREAD;  

  17.     private static final int SESSION_TIMEOUT = 10000;  

  18.     private static final String GROUP_PATH = "/disLocks";  

  19.     private static final String SUB_PATH = "/disLocks/sub";  

  20.     private static final String CONNECTION_STRING = "192.168.*.*:2181";  

  21.       

  22.     private static final int THREAD_NUM = 10;   

  23.     //确保连接zk成功;  

  24.     private CountDownLatch connectedSemaphore = new CountDownLatch(1);  

  25.     //确保所有线程运行结束;  

  26.     private static final CountDownLatch threadSemaphore = new CountDownLatch(THREAD_NUM);  

  27.     private static final Logger LOG = LoggerFactory.getLogger(AllZooKeeperWatcher.class);  

  28.     public DistributedLock(int id) {  

  29.         this.threadId = id;  

  30.         LOG_PREFIX_OF_THREAD = "【第"+threadId+"个线程】";  

  31.     }  

  32.     public static void main(String[] args) {  

  33.         for(int i=0; i < THREAD_NUM; i++){  

  34.             final int threadId = i+1;  

  35.             new Thread(){  

  36.                 @Override  

  37.                 public void run() {  

  38.                     try{  

  39.                         DistributedLock dc = new DistributedLock(threadId);  

  40.                         dc.createConnection(CONNECTION_STRING, SESSION_TIMEOUT);  

  41.                         //GROUP_PATH不存在的话,由一个线程创建即可;  

  42.                         synchronized (threadSemaphore){  

  43.                             dc.createPath(GROUP_PATH, "该节点由线程" + threadId + "创建"true);  

  44.                         }  

  45.                         dc.getLock();  

  46.                     } catch (Exception e){  

  47.                         LOG.error("【第"+threadId+"个线程】 抛出的异常:");  

  48.                         e.printStackTrace();  

  49.                     }  

  50.                 }  

  51.             }.start();  

  52.         }  

  53.         try {  

  54.             threadSemaphore.await();  

  55.             LOG.info("所有线程运行结束!");  

  56.         } catch (InterruptedException e) {  

  57.             e.printStackTrace();  

  58.         }  

  59.     }  

  60.     /** 

  61.      * 获取锁 

  62.      * @return 

  63.      */  

  64.     private void getLock() throws KeeperException, InterruptedException {  

  65.         selfPath = zk.create(SUB_PATH,null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);  

  66.         LOG.info(LOG_PREFIX_OF_THREAD+"创建锁路径:"+selfPath);  

  67.         if(checkMinPath()){  

  68.             getLockSuccess();  

  69.         }  

  70.     }  

  71.     /** 

  72.      * 创建节点 

  73.      * @param path 节点path 

  74.      * @param data 初始数据内容 

  75.      * @return 

  76.      */  

  77.     public boolean createPath( String path, String data, boolean needWatch) throws KeeperException, InterruptedException {  

  78.         if(zk.exists(path, needWatch)==null){  

  79.             LOG.info( LOG_PREFIX_OF_THREAD + "节点创建成功, Path: "  

  80.                     + this.zk.create( path,  

  81.                     data.getBytes(),  

  82.                     ZooDefs.Ids.OPEN_ACL_UNSAFE,  

  83.                     CreateMode.PERSISTENT )  

  84.                     + ", content: " + data );  

  85.         }  

  86.         return true;  

  87.     }  

  88.     /** 

  89.      * 创建ZK连接 

  90.      * @param connectString  ZK服务器地址列表 

  91.      * @param sessionTimeout Session超时时间 

  92.      */  

  93.     public void createConnection( String connectString, int sessionTimeout ) throws IOException, InterruptedException {  

  94.             zk = new ZooKeeper( connectString, sessionTimeout, this);  

  95.             connectedSemaphore.await();  

  96.     }  

  97.     /** 

  98.      * 获取锁成功 

  99.     */  

  100.     public void getLockSuccess() throws KeeperException, InterruptedException {  

  101.         if(zk.exists(this.selfPath,false) == null){  

  102.             LOG.error(LOG_PREFIX_OF_THREAD+"本节点已不在了...");  

  103.             return;  

  104.         }  

  105.         LOG.info(LOG_PREFIX_OF_THREAD + "获取锁成功,赶紧干活!");  

  106.         Thread.sleep(2000);  

  107.         LOG.info(LOG_PREFIX_OF_THREAD + "删除本节点:"+selfPath);  

  108.         zk.delete(this.selfPath, -1);  

  109.         releaseConnection();  

  110.         threadSemaphore.countDown();  

  111.     }  

  112.     /** 

  113.      * 关闭ZK连接 

  114.      */  

  115.     public void releaseConnection() {  

  116.         if ( this.zk !=null ) {  

  117.             try {  

  118.                 this.zk.close();  

  119.             } catch ( InterruptedException e ) {}  

  120.         }  

  121.         LOG.info(LOG_PREFIX_OF_THREAD + "释放连接");  

  122.     }  

  123.     /** 

  124.      * 检查自己是不是最小的节点 

  125.      * @return 

  126.      */  

  127.     public boolean checkMinPath() throws KeeperException, InterruptedException {  

  128.          List<String> subNodes = zk.getChildren(GROUP_PATH, false);  

  129.          Collections.sort(subNodes);  

  130.          int index = subNodes.indexOf( selfPath.substring(GROUP_PATH.length()+1));  

  131.          switch (index){  

  132.              case -1:{  

  133.                  LOG.error(LOG_PREFIX_OF_THREAD+"本节点已不在了..."+selfPath);  

  134.                  return false;  

  135.              }  

  136.              case 0:{  

  137.                  LOG.info(LOG_PREFIX_OF_THREAD+"子节点中,我果然是老大"+selfPath);  

  138.                  return true;  

  139.              }  

  140.              default:{  

  141.                  this.waitPath = GROUP_PATH +"/"+ subNodes.get(index - 1);  

  142.                  LOG.info(LOG_PREFIX_OF_THREAD+"获取子节点中,排在我前面的"+waitPath);  

  143.                  try{  

  144.                      zk.getData(waitPath, truenew Stat());  

  145.                      return false;  

  146.                  }catch(KeeperException e){  

  147.                      if(zk.exists(waitPath,false) == null){  

  148.                          LOG.info(LOG_PREFIX_OF_THREAD+"子节点中,排在我前面的"+waitPath+"已失踪,幸福来得太突然?");  

  149.                          return checkMinPath();  

  150.                      }else{  

  151.                          throw e;  

  152.                      }  

  153.                  }  

  154.              }  

  155.                    

  156.          }  

  157.        

  158.     }  

  159.     @Override  

  160.     public void process(WatchedEvent event) {  

  161.         if(event == null){  

  162.             return;  

  163.         }  

  164.         Event.KeeperState keeperState = event.getState();  

  165.         Event.EventType eventType = event.getType();  

  166.         if ( Event.KeeperState.SyncConnected == keeperState) {  

  167.             if ( Event.EventType.None == eventType ) {  

  168.                 LOG.info( LOG_PREFIX_OF_THREAD + "成功连接上ZK服务器" );  

  169.                 connectedSemaphore.countDown();  

  170.             }else if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {  

  171.                 LOG.info(LOG_PREFIX_OF_THREAD + "收到情报,排我前面的家伙已挂,我是不是可以出山了?");  

  172.                 try {  

  173.                     if(checkMinPath()){  

  174.                         getLockSuccess();  

  175.                     }  

  176.                 } catch (KeeperException e) {  

  177.                     e.printStackTrace();  

  178.                 } catch (InterruptedException e) {  

  179.                     e.printStackTrace();  

  180.                 }  

  181.             }  

  182.         }else if ( Event.KeeperState.Disconnected == keeperState ) {  

  183.             LOG.info( LOG_PREFIX_OF_THREAD + "与ZK服务器断开连接" );  

  184.         } else if ( Event.KeeperState.AuthFailed == keeperState ) {  

  185.             LOG.info( LOG_PREFIX_OF_THREAD + "权限检查失败" );  

  186.         } else if ( Event.KeeperState.Expired == keeperState ) {  

  187.             LOG.info( LOG_PREFIX_OF_THREAD + "会话失效" );  

  188.         }  

  189.     }  

  190. }  


log配置文件:

 

  1. # DEFAULT   

  2. log4j.rootLogger=INFO,CONSOLE  

  3.   

  4. #  

  5. # Log INFO level and above messages to the console  

  6. #  

  7. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender  

  8. log4j.appender.CONSOLE.Threshold=INFO  

  9. log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout  

  10. log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %m%n  

  11.   

  12.   

  13. log4j.appender.COMMONSTAT=org.apache.log4j.DailyRollingFileAppender  

  14. log4j.appender.COMMONSTAT.Threshold=INFO  

  15. log4j.appender.COMMONSTAT.File=/home/zookeeper/zookeeper-test-agent/logs/test.log  

  16. log4j.appender.COMMONSTAT.DatePattern='.'yyyy-MM-dd  

  17.   

  18. log4j.appender.COMMONSTAT.layout=org.apache.log4j.PatternLayout  

  19. log4j.appender.COMMONSTAT.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] - %m%n  

  20.   

  21. log4j.logger.org.displaytag=WARN  

  22. log4j.logger.org.apache.zookeeper=ERROR  

  23. log4j.logger.org.springframework=WARN  

  24. log4j.logger.org.I0Itec=WARN  

  25. log4j.logger.commonStat=INFO,COMMONSTAT  


运行结果:

 

  1. 2014-11-19 11:34:10,894 - 【第9个线程】成功连接上ZK服务器  

  2. 2014-11-19 11:34:10,895 - 【第8个线程】成功连接上ZK服务器  

  3. 2014-11-19 11:34:10,894 - 【第1个线程】成功连接上ZK服务器  

  4. 2014-11-19 11:34:10,894 - 【第7个线程】成功连接上ZK服务器  

  5. 2014-11-19 11:34:10,894 - 【第4个线程】成功连接上ZK服务器  

  6. 2014-11-19 11:34:10,895 - 【第5个线程】成功连接上ZK服务器  

  7. 2014-11-19 11:34:10,896 - 【第2个线程】成功连接上ZK服务器  

  8. 2014-11-19 11:34:10,894 - 【第10个线程】成功连接上ZK服务器  

  9. 2014-11-19 11:34:10,894 - 【第3个线程】成功连接上ZK服务器  

  10. 2014-11-19 11:34:10,895 - 【第6个线程】成功连接上ZK服务器  

  11. 2014-11-19 11:34:10,910 - 【第9个线程】节点创建成功, Path: /disLocks, content: 该节点由线程9创建  

  12. 2014-11-19 11:34:10,912 - 【第9个线程】创建锁路径:/disLocks/sub0000000000  

  13. 2014-11-19 11:34:10,917 - 【第6个线程】创建锁路径:/disLocks/sub0000000001  

  14. 2014-11-19 11:34:10,917 - 【第9个线程】子节点中,我果然是老大/disLocks/sub0000000000  

  15. 2014-11-19 11:34:10,921 - 【第3个线程】创建锁路径:/disLocks/sub0000000002  

  16. 2014-11-19 11:34:10,922 - 【第6个线程】获取子节点中,排在我前面的/disLocks/sub0000000000  

  17. 2014-11-19 11:34:10,923 - 【第9个线程】获取锁成功,赶紧干活!  

  18. 2014-11-19 11:34:10,924 - 【第10个线程】创建锁路径:/disLocks/sub0000000003  

  19. 2014-11-19 11:34:10,924 - 【第3个线程】获取子节点中,排在我前面的/disLocks/sub0000000001  

  20. 2014-11-19 11:34:10,928 - 【第10个线程】获取子节点中,排在我前面的/disLocks/sub0000000002  

  21. 2014-11-19 11:34:10,929 - 【第1个线程】创建锁路径:/disLocks/sub0000000004  

  22. 2014-11-19 11:34:10,932 - 【第5个线程】创建锁路径:/disLocks/sub0000000005  

  23. 2014-11-19 11:34:10,935 - 【第1个线程】获取子节点中,排在我前面的/disLocks/sub0000000003  

  24. 2014-11-19 11:34:10,936 - 【第2个线程】创建锁路径:/disLocks/sub0000000006  

  25. 2014-11-19 11:34:10,936 - 【第5个线程】获取子节点中,排在我前面的/disLocks/sub0000000004  

  26. 2014-11-19 11:34:10,940 - 【第4个线程】创建锁路径:/disLocks/sub0000000007  

  27. 2014-11-19 11:34:10,941 - 【第2个线程】获取子节点中,排在我前面的/disLocks/sub0000000005  

  28. 2014-11-19 11:34:10,943 - 【第8个线程】创建锁路径:/disLocks/sub0000000008  

  29. 2014-11-19 11:34:10,944 - 【第4个线程】获取子节点中,排在我前面的/disLocks/sub0000000006  

  30. 2014-11-19 11:34:10,945 - 【第7个线程】创建锁路径:/disLocks/sub0000000009  

  31. 2014-11-19 11:34:10,946 - 【第8个线程】获取子节点中,排在我前面的/disLocks/sub0000000007  

  32. 2014-11-19 11:34:10,947 - 【第7个线程】获取子节点中,排在我前面的/disLocks/sub0000000008  

  33. 2014-11-19 11:34:12,923 - 【第9个线程】删除本节点:/disLocks/sub0000000000  

  34. 2014-11-19 11:34:12,926 - 【第6个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  35. 2014-11-19 11:34:12,928 - 【第6个线程】子节点中,我果然是老大/disLocks/sub0000000001  

  36. 2014-11-19 11:34:12,930 - 【第9个线程】释放连接  

  37. 2014-11-19 11:34:12,930 - 【第6个线程】获取锁成功,赶紧干活!  

  38. 2014-11-19 11:34:14,930 - 【第6个线程】删除本节点:/disLocks/sub0000000001  

  39. 2014-11-19 11:34:14,937 - 【第3个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  40. 2014-11-19 11:34:14,941 - 【第3个线程】子节点中,我果然是老大/disLocks/sub0000000002  

  41. 2014-11-19 11:34:14,943 - 【第6个线程】释放连接  

  42. 2014-11-19 11:34:14,946 - 【第3个线程】获取锁成功,赶紧干活!  

  43. 2014-11-19 11:34:16,946 - 【第3个线程】删除本节点:/disLocks/sub0000000002  

  44. 2014-11-19 11:34:16,949 - 【第10个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  45. 2014-11-19 11:34:16,951 - 【第10个线程】子节点中,我果然是老大/disLocks/sub0000000003  

  46. 2014-11-19 11:34:16,953 - 【第3个线程】释放连接  

  47. 2014-11-19 11:34:16,953 - 【第10个线程】获取锁成功,赶紧干活!  

  48. 2014-11-19 11:34:18,953 - 【第10个线程】删除本节点:/disLocks/sub0000000003  

  49. 2014-11-19 11:34:18,957 - 【第1个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  50. 2014-11-19 11:34:18,960 - 【第10个线程】释放连接  

  51. 2014-11-19 11:34:18,961 - 【第1个线程】子节点中,我果然是老大/disLocks/sub0000000004  

  52. 2014-11-19 11:34:18,964 - 【第1个线程】获取锁成功,赶紧干活!  

  53. 2014-11-19 11:34:20,964 - 【第1个线程】删除本节点:/disLocks/sub0000000004  

  54. 2014-11-19 11:34:20,967 - 【第5个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  55. 2014-11-19 11:34:20,969 - 【第5个线程】子节点中,我果然是老大/disLocks/sub0000000005  

  56. 2014-11-19 11:34:20,971 - 【第1个线程】释放连接  

  57. 2014-11-19 11:34:20,971 - 【第5个线程】获取锁成功,赶紧干活!  

  58. 2014-11-19 11:34:22,971 - 【第5个线程】删除本节点:/disLocks/sub0000000005  

  59. 2014-11-19 11:34:22,974 - 【第2个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  60. 2014-11-19 11:34:22,978 - 【第2个线程】子节点中,我果然是老大/disLocks/sub0000000006  

  61. 2014-11-19 11:34:22,979 - 【第5个线程】释放连接  

  62. 2014-11-19 11:34:22,981 - 【第2个线程】获取锁成功,赶紧干活!  

  63. 2014-11-19 11:34:24,981 - 【第2个线程】删除本节点:/disLocks/sub0000000006  

  64. 2014-11-19 11:34:24,985 - 【第4个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  65. 2014-11-19 11:34:24,989 - 【第2个线程】释放连接  

  66. 2014-11-19 11:34:24,989 - 【第4个线程】子节点中,我果然是老大/disLocks/sub0000000007  

  67. 2014-11-19 11:34:24,995 - 【第4个线程】获取锁成功,赶紧干活!  

  68. 2014-11-19 11:34:26,995 - 【第4个线程】删除本节点:/disLocks/sub0000000007  

  69. 2014-11-19 11:34:26,998 - 【第8个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  70. 2014-11-19 11:34:27,000 - 【第8个线程】子节点中,我果然是老大/disLocks/sub0000000008  

  71. 2014-11-19 11:34:27,004 - 【第8个线程】获取锁成功,赶紧干活!  

  72. 2014-11-19 11:34:27,004 - 【第4个线程】释放连接  

  73. 2014-11-19 11:34:29,004 - 【第8个线程】删除本节点:/disLocks/sub0000000008  

  74. 2014-11-19 11:34:29,007 - 【第7个线程】收到情报,排我前面的家伙已挂,我是不是可以出山了?  

  75. 2014-11-19 11:34:29,009 - 【第7个线程】子节点中,我果然是老大/disLocks/sub0000000009  

  76. 2014-11-19 11:34:29,010 - 【第8个线程】释放连接  

  77. 2014-11-19 11:34:29,011 - 【第7个线程】获取锁成功,赶紧干活!  

  78. 2014-11-19 11:34:31,011 - 【第7个线程】删除本节点:/disLocks/sub0000000009  

  79. 2014-11-19 11:34:31,017 - 【第7个线程】释放连接  

  80. 2014-11-19 11:34:31,017 - 所有线程运行结束!  

  81.  

 

 

转载于:https://my.oschina.net/rock912/blog/473536

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值