Zookeeper系列(二十九)Zookeeper场景应用之分布式锁实现

来源:互联网 发布:淘宝店招制作视频 编辑:程序博客网 时间:2024/06/05 10:00

1、使用场景

分布式锁主要应用在跨主机,跨网络资源访问中的协调控制,例如多个task处理任务,但是要求一个任务只能限制在一台task上处理,这里就可以采取分布式锁来实现访问控制协调。

2、实现逻辑


基本思想:通过创建一个持久化根节点作为本次锁行为根节点,每个需要获取资源的分布式业务注册一个临时顺序节点挂载到根节点下,每个分布式业务通过节点的顺序来获取锁。在通过顺序节点获取锁的时候有两种方式:

方式1:简单轮询,只要没有超时限制,就不断尝试获取锁,直到获取到了就返回。

方式2:通过监听前一个顺序节点的方式,只要前一个顺序节点释放(删除了节点)就再次获取,否则就阻塞等待,这种方式存在并发竞争问题,例如,节点已经释放后,再挂载监听此节点的事件,就会导致事件一直不会触发,不断等待下去。

3、代码实现

接口定义如下
[java] view plain copy
  1. /** 
  2.  *  
  3.  */  
  4. package com.flykingmz.zookeeper.dLock;  
  5.   
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. /** 
  9.  * @author flyking 
  10.  *  
  11.  */  
  12. public interface DistributedLock {  
  13.     /** 
  14.      * 获取锁,如果没有得到就一直等待 
  15.      * @throws Exception 
  16.      */  
  17.     void lock() throws Exception;  
  18.   
  19.     /** 
  20.      * 获取锁,直到指定时间time超时返回 
  21.      * @param time 
  22.      * @param unit 
  23.      * @return 
  24.      * @throws Exception 
  25.      */  
  26.     boolean lock(long time, TimeUnit unit) throws Exception;  
  27.   
  28.     /** 
  29.      * 释放锁 
  30.      * @throws Exception 
  31.      */  
  32.     void unLock() throws Exception;  
  33.       
  34.     /** 
  35.      * 释放根节点锁 
  36.      * @throws Exception 
  37.      */  
  38.     void release() throws Exception;  
  39. }  
简单实现类
[java] view plain copy
  1. /** 
  2.  *  
  3.  */  
  4. package com.flykingmz.zookeeper.dLock;  
  5.   
  6. import java.util.Collections;  
  7. import java.util.Comparator;  
  8. import java.util.List;  
  9. import java.util.concurrent.CountDownLatch;  
  10. import java.util.concurrent.TimeUnit;  
  11.   
  12. import org.I0Itec.zkclient.IZkChildListener;  
  13. import org.I0Itec.zkclient.IZkDataListener;  
  14. import org.I0Itec.zkclient.ZkClient;  
  15. import org.I0Itec.zkclient.exception.ZkNoNodeException;  
  16. import org.slf4j.Logger;  
  17. import org.slf4j.LoggerFactory;  
  18.   
  19. /** 
  20.  * 实现的一个基于zookeeper的简单分布式锁实现 
  21.  * 初始化需要传递zookeeper的host+port 
  22.  * @author flyking 
  23.  *  
  24.  */  
  25. public class SimpleDistributedLock implements DistributedLock {  
  26.     private final static Logger logger = LoggerFactory  
  27.             .getLogger(SimpleDistributedLock.class);  
  28.   
  29.     private ZkClient client;  
  30.     private String rootLockerName;  
  31.     private String SUB_LOCK_NAME_PREFIX = "sublock-";  
  32.     private ThreadLocal<String> currentLockPath = new ThreadLocal<String>();  
  33.   
  34.     public SimpleDistributedLock(String serverstring, String rootLockerName) {  
  35.         this.client = new ZkClient(serverstring);  
  36.         this.rootLockerName = rootLockerName;  
  37.         this.createRootLock();  
  38.     }  
  39.   
  40.     /** 
  41.      * 基于临时序列节点创建的一个锁 
  42.      */  
  43.     private void createLock() {  
  44.         String selfPath = client.createEphemeralSequential(rootLockerName.concat("/").concat(SUB_LOCK_NAME_PREFIX), 1);  
  45.         logger.info("thread {},create Lock and path is {}",Thread.currentThread().getId(),selfPath);  
  46.         currentLockPath.set(selfPath.substring(selfPath.lastIndexOf("/")+1));  
  47.     }  
  48.   
  49.     /** 
  50.      * 基于持久节点创建的锁的根节点 
  51.      */  
  52.     private void createRootLock() {  
  53.         this.client.createPersistent(rootLockerName);  
  54.     }  
  55.   
  56.     /** 
  57.      * 删除锁 
  58.      */  
  59.     private void deleteLock() {  
  60.         String selfPath = currentLockPath.get();  
  61.         client.delete(rootLockerName.concat("/").concat(  
  62.                 selfPath));  
  63.         logger.info("thread {},delete Lock and path is {} and time {}",Thread.currentThread().getId(),rootLockerName.concat("/").concat(  
  64.                 selfPath),System.currentTimeMillis());  
  65.     }  
  66.   
  67.     /** 
  68.      * 尝试获取锁,如果有返回已获取,如果锁被占用就返回未获取 
  69.      * @return 
  70.      */  
  71.     private boolean tryLock() {  
  72.         List<String> sortedLocks = this.getSortedChildren();  
  73.         String lockPath = currentLockPath.get();  
  74.         if (sortedLocks.indexOf(lockPath) == 0) {  
  75.             logger.info("thread {} try get Lock ",Thread.currentThread().getId());  
  76.             return true;  
  77.         }  
  78.         return false;  
  79.     }  
  80.   
  81.     /** 
  82.      * 获取排序好的子节点 
  83.      * @return 
  84.      */  
  85.     private List<String> getSortedChildren() {  
  86.         try {  
  87.             List<String> children = client.getChildren(rootLockerName);  
  88.             Collections.sort(children, new Comparator<String>() {  
  89.                 public int compare(String lhs, String rhs) {  
  90.                     return getLockNodeNumber(lhs).compareTo(  
  91.                             getLockNodeNumber(rhs));  
  92.                 }  
  93.             });  
  94.             return children;  
  95.   
  96.         } catch (ZkNoNodeException e) {  
  97.             this.createRootLock();  
  98.             return getSortedChildren();  
  99.         }  
  100.     }  
  101.   
  102.     /** 
  103.      * 截取锁编号 
  104.      * @param str zk的临时序列节点名称 
  105.      * @return 
  106.      */  
  107.     private String getLockNodeNumber(String str) {  
  108.         int index = str.lastIndexOf(SUB_LOCK_NAME_PREFIX);  
  109.         if (index >= 0) {  
  110.             return index <= str.length() ? str.substring(index) : "";  
  111.         }  
  112.         return str;  
  113.     }  
  114.   
  115.     /** 
  116.      * 阻塞获取锁,存在锁释放和锁监听的并发竞争问题 
  117.      * 需要优化 
  118.      * @param timeToWait 
  119.      * 阻塞最大等待的时间(ms) 
  120.      * @param usePollMode 
  121.      * 是否采用简单轮询模式,还是阻塞等待模式 
  122.      * @return 
  123.      */  
  124.     @Deprecated  
  125.     private boolean blockLock(long timeToWait, boolean usePollMode) {  
  126.         logger.info("thread {} enter block Lock ",Thread.currentThread().getId());  
  127.         if(usePollMode){  
  128.             return this.blockLock(timeToWait);  
  129.         }  
  130.         long starTime = System.currentTimeMillis();  
  131.         while ((System.currentTimeMillis() - starTime) < timeToWait) {  
  132.             boolean getLock = this.tryLock();  
  133.             if(getLock){  
  134.                 logger.info("thread {} block get Lock ",Thread.currentThread().getId());  
  135.                 return true;  
  136.             }  
  137.             if (!getLock) {  
  138.                 List<String> sortedLocks = this.getSortedChildren();  
  139.                 String lockPath = currentLockPath.get();  
  140.                 if(sortedLocks.indexOf(lockPath) == 0){  
  141.                     logger.info("thread {} block get Lock ",Thread.currentThread().getId());  
  142.                     return true;  
  143.                 }  
  144.                 int preIndex = sortedLocks.indexOf(lockPath) - 1;  
  145.                 String preIndexPath = sortedLocks.get(preIndex);  
  146.                 String preSequencePath = rootLockerName.concat("/").concat(  
  147.                         preIndexPath);  
  148.                 logger.info("thread {} start listen {} and time {} ",Thread.currentThread().getId(),preSequencePath,System.currentTimeMillis());  
  149.                 final CountDownLatch latch = new CountDownLatch(1);  
  150.                 IZkDataListener dataListener = new IZkDataListener() {  
  151.                       
  152.                     public void handleDataDeleted(String dataPath) throws Exception {  
  153.                         logger.info("data path {} handleDataDeleted ",dataPath);                                      
  154.                     }  
  155.                       
  156.                     public void handleDataChange(String dataPath, Object data) throws Exception {  
  157.                         logger.info("data path {} handleDataChange ",dataPath);                       
  158.                     }  
  159.                 };  
  160.                   
  161.                 try {  
  162.                     client.subscribeDataChanges(preSequencePath,dataListener);  
  163.                     logger.info("thread {} start wait {} and time {}",Thread.currentThread().getId(),rootLockerName,System.currentTimeMillis());  
  164.                     latch.await();  
  165.                 } catch (ZkNoNodeException e){  
  166.                     logger.info("zk client ZkNoNodeException error!", e);  
  167.                     client.subscribeDataChanges(preSequencePath, dataListener);  
  168.                 }catch (Exception e) {  
  169.                 }  
  170.   
  171.             } else {  
  172.                 logger.info("thread {} block get Lock ",Thread.currentThread().getId());  
  173.                 return true;  
  174.             }  
  175.   
  176.         }  
  177.         return false;  
  178.     }  
  179.       
  180.     /** 
  181.      * 阻塞等待获取锁,简单的轮询实现 
  182.      * @param timeToWait 
  183.      * 阻塞最大等待的时间(ms) 
  184.      * @return 
  185.      */  
  186.     private boolean blockLock(long timeToWait) {  
  187.         logger.info("thread {} enter block Lock ",Thread.currentThread().getId());  
  188.         long starTime = System.currentTimeMillis();  
  189.         while ((System.currentTimeMillis() - starTime) < timeToWait) {  
  190.             boolean getLock = this.tryLock();  
  191.             if(getLock){  
  192.                 logger.info("thread {} block get Lock ",Thread.currentThread().getId());  
  193.                 return true;  
  194.             }  
  195.         }  
  196.         return false;  
  197.     }  
  198.   
  199.     public void lock() throws Exception {  
  200.         this.createLock();  
  201.         boolean isLock = this.tryLock();  
  202.         if (isLock) {  
  203.             return;  
  204.         }  
  205.         this.blockLock(Long.MAX_VALUE);  
  206.     }  
  207.   
  208.     public boolean lock(long time, TimeUnit unit) throws Exception {  
  209.         this.createLock();  
  210.         boolean isLock = this.tryLock();  
  211.         if (isLock) {  
  212.             return true;  
  213.         }  
  214.         isLock = this.blockLock(unit.toMillis(time));  
  215.         if (isLock) {  
  216.             return true;  
  217.         }  
  218.         return false;  
  219.     }  
  220.   
  221.     public void unLock() throws Exception {  
  222.         this.deleteLock();  
  223.     }  
  224.   
  225.     public void release() throws Exception {  
  226.         this.client.delete(rootLockerName);  
  227.         this.client.close();  
  228.     }  
  229.   
  230. }  
测试代码类
[java] view plain copy
  1. package com.flykingmz.zookeeper.dLock;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4.   
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7.   
  8. public class TestSimpleDistributedLock {  
  9.     private final static Logger logger = LoggerFactory  
  10.             .getLogger(TestSimpleDistributedLock.class);  
  11.       
  12.     private static final int THREAD_NUM = 100;   
  13.     private static final CountDownLatch threadSemaphore = new CountDownLatch(THREAD_NUM);    
  14.   
  15.     /** 
  16.      * @param args 
  17.      * @throws InterruptedException  
  18.      */  
  19.     public static void main(String[] args) throws InterruptedException {  
  20.         final DistributedLock dc = new SimpleDistributedLock("host:port","/rootLock");    
  21.          for(int i=0; i < THREAD_NUM; i++){    
  22.                 new Thread(){    
  23.                     @Override    
  24.                     public void run() {    
  25.                         try{    
  26.                             logger.info("thread start no "+Thread.currentThread().getId());  
  27.                             dc.lock();  
  28.                             dc.unLock();  
  29.                             threadSemaphore.countDown();  
  30.                         } catch (Exception e){    
  31.                             e.printStackTrace();    
  32.                         }    
  33.                     }    
  34.                 }.start();    
  35.             }    
  36.             try {    
  37.                 threadSemaphore.await();    
  38.                 dc.release();  
  39.                 logger.info("所有线程运行结束!");    
  40.             } catch (InterruptedException e) {    
  41.                 e.printStackTrace();    
  42.             } catch (Exception e) {  
  43.                 e.printStackTrace();  
  44.             }   
  45.     }  
  46.   
  47. }  

4、结果

10个线程模拟分布式业务获取锁,日志打印如下:
[html] view plain copy
  1. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 12  
  2. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 15  
  3. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 16  
  4. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 14  
  5. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 19  
  6. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 13  
  7. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 20  
  8. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 18  
  9. 2016-10-20 18:07:04 [Thread-6] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 17  
  10. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - thread start no 21  
  11. 2016-10-20 18:07:04 [Thread-6] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 17,create Lock and path is /rootLock/sublock-0000000000  
  12. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 14,create Lock and path is /rootLock/sublock-0000000001  
  13. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 12,create Lock and path is /rootLock/sublock-0000000002  
  14. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 19,create Lock and path is /rootLock/sublock-0000000003  
  15. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 16,create Lock and path is /rootLock/sublock-0000000004  
  16. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 13,create Lock and path is /rootLock/sublock-0000000005  
  17. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 18,create Lock and path is /rootLock/sublock-0000000006  
  18. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 15,create Lock and path is /rootLock/sublock-0000000007  
  19. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 20,create Lock and path is /rootLock/sublock-0000000008  
  20. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 21,create Lock and path is /rootLock/sublock-0000000009  
  21. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 14 enter block Lock   
  22. 2016-10-20 18:07:04 [Thread-6] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 17 try get Lock   
  23. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 12 enter block Lock   
  24. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 19 enter block Lock   
  25. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 16 enter block Lock   
  26. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 13 enter block Lock   
  27. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 18 enter block Lock   
  28. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 15 enter block Lock   
  29. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 20 enter block Lock   
  30. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 21 enter block Lock   
  31. 2016-10-20 18:07:04 [Thread-6] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 17,delete Lock and path is /rootLock/sublock-0000000000 and time 1476958024362  
  32. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 14 try get Lock   
  33. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 14 block get Lock   
  34. 2016-10-20 18:07:04 [Thread-3] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 14,delete Lock and path is /rootLock/sublock-0000000001 and time 1476958024377  
  35. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 12 try get Lock   
  36. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 12 block get Lock   
  37. 2016-10-20 18:07:04 [Thread-1] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 12,delete Lock and path is /rootLock/sublock-0000000002 and time 1476958024393  
  38. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 19 try get Lock   
  39. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 19 block get Lock   
  40. 2016-10-20 18:07:04 [Thread-8] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 19,delete Lock and path is /rootLock/sublock-0000000003 and time 1476958024408  
  41. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 16 try get Lock   
  42. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 16 block get Lock   
  43. 2016-10-20 18:07:04 [Thread-5] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 16,delete Lock and path is /rootLock/sublock-0000000004 and time 1476958024408  
  44. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 13 try get Lock   
  45. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 13 block get Lock   
  46. 2016-10-20 18:07:04 [Thread-2] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 13,delete Lock and path is /rootLock/sublock-0000000005 and time 1476958024424  
  47. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 18 try get Lock   
  48. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 18 block get Lock   
  49. 2016-10-20 18:07:04 [Thread-7] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 18,delete Lock and path is /rootLock/sublock-0000000006 and time 1476958024440  
  50. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 15 try get Lock   
  51. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 15 block get Lock   
  52. 2016-10-20 18:07:04 [Thread-4] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 15,delete Lock and path is /rootLock/sublock-0000000007 and time 1476958024440  
  53. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 20 try get Lock   
  54. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 20 block get Lock   
  55. 2016-10-20 18:07:04 [Thread-9] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 20,delete Lock and path is /rootLock/sublock-0000000008 and time 1476958024455  
  56. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 21 try get Lock   
  57. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 21 block get Lock   
  58. 2016-10-20 18:07:04 [Thread-10] INFO com.flykingmz.zookeeper.dLock.SimpleDistributedLock  - thread 21,delete Lock and path is /rootLock/sublock-0000000009 and time 1476958024455  
  59. 2016-10-20 18:07:04 [ZkClient-EventThread-9-172.16.10.58:2181] INFO org.I0Itec.zkclient.ZkEventThread  - Terminate ZkClient event thread.  
  60. 2016-10-20 18:07:04 [main-EventThread] INFO org.apache.zookeeper.ClientCnxn  - EventThread shut down  
  61. 2016-10-20 18:07:04 [main] INFO org.apache.zookeeper.ZooKeeper  - Session: 0x25776ad02740082 closed  
  62. 2016-10-20 18:07:04 [main] INFO com.flykingmz.zookeeper.dLock.TestSimpleDistributedLock  - 所有线程运行结束!  

基于zookeeper实现的分布式锁全部源代码可以到github上下载,地址:https://github.com/flykingmz/zookeeper-step.git,具体项目名称为:distributedLock


0 0