基于Zookeeper的分布式共享锁

首先,说说我们的场景,订单服务是做成集群的,当两个以上结点同时收到一个相同订单的创建指令,这时并发就产生了,系统就会重复创建订单。等等......场景。这时,分布式共享锁就闪亮登场了。

 

共享锁在同一个进程中是很容易实现的,但在跨进程或者在不同Server之间就不好实现了。Zookeeper就很容易实现。具体的实现原理官网和其它网站也有翻译,这里就不在赘述了。

 

官网资料:http://zookeeper.apache.org/doc/r3.4.5/recipes.html

中文资料:https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper

详见Locks章节。

 

原理都知道了,网上一搜索Apache上面已经有提供了,既然已经有轮子了,哪我们也没必要重复造轮子了吧!直接使用Curator。但是,我们在测试中发现,用于共享锁的结点无法自动回收,除了最末一级的临时结点会在锁释放和session超时的时候能自动回收外,其它结点均无法自动回收。我们的订单一天有好几万,遇到618和双十一的时候每天的订单量超50W,如果结点长期不回收的话,肯定会影响Zookeeper的性能。这时,我们就想到了一句话“自己动手,丰衣足食”。下面直接上代码:

 

首先,创建一个Maven工程,在pom文件里导入下面的包:

Xml代码   收藏代码
  1. <dependencies>  
  2.     <dependency>  
  3.         <groupId>org.apache.zookeeper</groupId>  
  4.         <artifactId>zookeeper</artifactId>  
  5.         <version>3.4.6</version>  
  6.     </dependency>  
  7.     <dependency>  
  8.         <groupId>org.apache.curator</groupId>  
  9.         <artifactId>curator-client</artifactId>  
  10.         <version>2.8.0</version>  
  11.     </dependency>  
  12.     <dependency>  
  13.         <groupId>org.apache.curator</groupId>  
  14.         <artifactId>curator-recipes</artifactId>  
  15.         <version>2.8.0</version>  
  16.     </dependency>  
  17.     <dependency>  
  18.         <groupId>org.apache.curator</groupId>  
  19.         <artifactId>curator-framework</artifactId>  
  20.         <version>2.8.0</version>  
  21.     </dependency>  
  22.     <dependency>  
  23.         <groupId>commons-beanutils</groupId>  
  24.         <artifactId>commons-beanutils</artifactId>  
  25.         <version>1.9.2</version>  
  26.     </dependency>  
  27.     <dependency>  
  28.         <groupId>commons-logging</groupId>  
  29.         <artifactId>commons-logging</artifactId>  
  30.         <version>1.2</version>  
  31.     </dependency>  
  32.     <dependency>  
  33.         <groupId>commons-lang</groupId>  
  34.         <artifactId>commons-lang</artifactId>  
  35.         <version>2.6</version>  
  36.     </dependency>  
  37. </dependencies>  

 

LockZookeeperClient接口:

Java代码   收藏代码
  1. package com.XXX.framework.lock;  
  2.   
  3. import org.apache.curator.framework.CuratorFramework;  
  4.   
  5. /** 
  6.  *  
  7.  * description 
  8.  *  
  9.  * @author Roadrunners 
  10.  * @version 1.0, 2015年7月9日 
  11.  */  
  12. public interface LockZookeeperClient {  
  13.     /** 
  14.      *  
  15.      * @return  
  16.      */  
  17.     CuratorFramework getCuratorFramework();  
  18.   
  19.     /** 
  20.      *  
  21.      * @return  
  22.      */  
  23.     String getBasePath();  
  24.   
  25.     /** 
  26.      * garbage collector 
  27.      *  
  28.      * @param gcPath 
  29.      */  
  30.     void gc(String gcPath);  
  31. }  

 

LockZookeeperClient接口的实现LockZookeeperClientFactory:

Java代码   收藏代码
  1. package com.XXX.framework.lock;  
  2.   
  3. import java.util.Date;  
  4. import java.util.List;  
  5. import java.util.Timer;  
  6. import java.util.TimerTask;  
  7. import java.util.concurrent.ConcurrentSkipListSet;  
  8.   
  9. import org.apache.commons.collections.CollectionUtils;  
  10. import org.apache.commons.lang.StringUtils;  
  11. import org.apache.commons.logging.Log;  
  12. import org.apache.commons.logging.LogFactory;  
  13. import org.apache.curator.framework.CuratorFramework;  
  14. import org.apache.curator.framework.CuratorFrameworkFactory;  
  15. import org.apache.curator.retry.ExponentialBackoffRetry;  
  16.   
  17. /** 
  18.  *  
  19.  * description 
  20.  *   
  21.  * @author Roadrunners 
  22.  * @version 1.0, 2015年7月9日 
  23.  */  
  24. public class LockZookeeperClientFactory implements LockZookeeperClient {  
  25.     private static final Log LOG = LogFactory.getLog(LockZookeeperClientFactory.class);  
  26.   
  27.     private boolean hasGc = true;  
  28.     private Timer gcTimer;  
  29.     private TimerTask gcTimerTask;  
  30.     private ConcurrentSkipListSet<String> gcPaths = new ConcurrentSkipListSet<String>();  
  31.     private int gcIntervalSecond = 60;  
  32.   
  33.     private CuratorFramework curatorFramework;  
  34.     private String zookeeperIpPort = "localhost:2181";  
  35.     private int sessionTimeoutMs = 10000;  
  36.     private int connectionTimeoutMs = 10000;  
  37.     private String basePath = "/locks";  
  38.   
  39.     public void setHasGc(boolean hasGc) {  
  40.         this.hasGc = hasGc;  
  41.     }  
  42.   
  43.     public void setGcIntervalSecond(int gcIntervalSecond) {  
  44.         this.gcIntervalSecond = gcIntervalSecond;  
  45.     }  
  46.   
  47.     public void setZookeeperIpPort(String zookeeperIpPort) {  
  48.         this.zookeeperIpPort = zookeeperIpPort;  
  49.     }  
  50.   
  51.     public void setSessionTimeoutMs(int sessionTimeoutMs) {  
  52.         this.sessionTimeoutMs = sessionTimeoutMs;  
  53.     }  
  54.   
  55.     public void setConnectionTimeoutMs(int connectionTimeoutMs) {  
  56.         this.connectionTimeoutMs = connectionTimeoutMs;  
  57.     }  
  58.   
  59.     public void setBasePath(String basePath) {  
  60.         basePath = basePath.trim();  
  61.         if (basePath.endsWith("/")) {  
  62.             basePath = basePath.substring(0, basePath.length() - 1);  
  63.         }  
  64.   
  65.         this.basePath = basePath;  
  66.     }  
  67.   
  68.     public void init() {  
  69.         if(StringUtils.isBlank(zookeeperIpPort)){  
  70.             throw new NullPointerException("zookeeperIpPort");  
  71.         }  
  72.   
  73.         ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(10003);  
  74.         curatorFramework = CuratorFrameworkFactory.newClient(zookeeperIpPort.trim(), sessionTimeoutMs, connectionTimeoutMs, retryPolicy);  
  75.         curatorFramework.start();  
  76.         LOG.info("CuratorFramework initialise succeed.");  
  77.   
  78.         if (hasGc) {  
  79.             gc();  
  80.         }  
  81.     }  
  82.   
  83.     public void destroy() {  
  84.         gcPaths.clear();  
  85.         gcPaths = null;  
  86.         gcStop();  
  87.         curatorFramework.close();  
  88.         curatorFramework = null;  
  89.     }  
  90.   
  91.     @Override  
  92.     public void gc(String gcPath) {  
  93.         if (hasGc && StringUtils.isNotBlank(gcPath)) {  
  94.             gcPaths.add(gcPath.trim());  
  95.         }  
  96.     }  
  97.   
  98.     @Override  
  99.     public CuratorFramework getCuratorFramework() {  
  100.         return this.curatorFramework;  
  101.     }  
  102.   
  103.     @Override  
  104.     public String getBasePath() {  
  105.         return this.basePath;  
  106.     }  
  107.   
  108.     private synchronized void gc() {  
  109.         gcStop();  
  110.   
  111.         try {  
  112.             scanningGCNodes();  
  113.         } catch (Throwable e) {  
  114.             LOG.warn(e);  
  115.         }  
  116.   
  117.         gcTimerTask = new TimerTask() {  
  118.             @Override  
  119.             public void run() {  
  120.                 doingGc();  
  121.             }  
  122.         };  
  123.   
  124.         Date begin = new Date();  
  125.         begin.setTime(begin.getTime() + (10 * 1000L));  
  126.         gcTimer = new Timer("lock-gc"true);  
  127.         gcTimer.schedule(gcTimerTask, begin, gcIntervalSecond * 1000L);  
  128.     }  
  129.   
  130.     private synchronized void gcStop() {  
  131.         if (null != gcTimer) {  
  132.             gcTimer.cancel();  
  133.             gcTimer = null;  
  134.         }  
  135.   
  136.         if (null != gcTimerTask) {  
  137.             gcTimerTask.cancel();  
  138.             gcTimerTask = null;  
  139.         }  
  140.     }  
  141.   
  142.     private synchronized void scanningGCNodes() throws Exception {  
  143.         if (null == curatorFramework.checkExists().forPath(basePath)) {  
  144.             return;  
  145.         }  
  146.   
  147.         List<String> paths = curatorFramework.getChildren().forPath(basePath);  
  148.         if (CollectionUtils.isEmpty(paths)) {  
  149.             gcPaths.add(basePath);  
  150.             return;  
  151.         }  
  152.   
  153.         for (String path : paths) {  
  154.             try{  
  155.                 String tmpPath = basePath + "/" + path;  
  156.                 if (null == curatorFramework.checkExists().forPath(tmpPath)) {  
  157.                     continue;  
  158.                 }  
  159.                   
  160.                 gcPaths.add(tmpPath);  
  161.             } catch(Throwable e){  
  162.                 LOG.warn("scanning gc nodes error.", e);  
  163.             }  
  164.         }  
  165.     }  
  166.       
  167.     private synchronized void doingGc() {  
  168.         LOG.debug("GC beginning.");  
  169.   
  170.         if (CollectionUtils.isNotEmpty(gcPaths)) {  
  171.             for (String path : gcPaths) {  
  172.                 try {  
  173.                     if (null != curatorFramework.checkExists().forPath(path)) {  
  174.                         if (CollectionUtils.isEmpty(curatorFramework.getChildren().forPath(path))) {  
  175.                             curatorFramework.delete().forPath(path);  
  176.                             gcPaths.remove(path);  
  177.                             LOG.debug("GC " + path);  
  178.                         }  
  179.                     } else {  
  180.                         gcPaths.remove(path);  
  181.                     }  
  182.                 } catch (Throwable e) {  
  183.                     gcPaths.remove(path);  
  184.                     LOG.warn(e);  
  185.                 }  
  186.             }  
  187.         }  
  188.   
  189.         LOG.debug("GC ended.");  
  190.     }  
  191.   
  192. }  

 

SharedLock共享锁:

Java代码   收藏代码
  1. package com.XXX.framework.lock.shared;  
  2.   
  3. import java.util.concurrent.TimeUnit;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.curator.framework.recipes.locks.InterProcessLock;  
  7. import org.apache.curator.framework.recipes.locks.InterProcessMutex;  
  8.   
  9. import com.XXX.framework.lock.LockZookeeperClient;  
  10.   
  11. /** 
  12.  *  
  13.  * description 
  14.  *  
  15.  * @author Roadrunners 
  16.  * @version 1.0, 2015年7月9日 
  17.  */  
  18. public class SharedLock {  
  19.     private InterProcessLock interProcessLock;  
  20.   
  21.     public SharedLock(LockZookeeperClient lockZookeeperClient, String resourceId) {  
  22.         super();  
  23.           
  24.         if (StringUtils.isBlank(resourceId)) {  
  25.             throw new NullPointerException("resourceId");  
  26.         }  
  27.         String path = lockZookeeperClient.getBasePath();  
  28.         path += ("/" + resourceId.trim());  
  29.   
  30.         interProcessLock = new InterProcessMutex(lockZookeeperClient.getCuratorFramework(), path);  
  31.         lockZookeeperClient.gc(path);  
  32.     }  
  33.       
  34.     /** 
  35.      * Acquire the mutex - blocking until it's available. Each call to acquire must be balanced by a call 
  36.      * to {@link #release()} 
  37.      * 
  38.      * @throws Exception ZK errors, connection interruptions 
  39.      */  
  40.     public void acquire() throws Exception {  
  41.         interProcessLock.acquire();  
  42.     }  
  43.   
  44.     /** 
  45.      * Acquire the mutex - blocks until it's available or the given time expires. Each call to acquire that returns true must be balanced by a call 
  46.      * to {@link #release()} 
  47.      * 
  48.      * @param time time to wait 
  49.      * @param unit time unit 
  50.      * @return true if the mutex was acquired, false if not 
  51.      * @throws Exception ZK errors, connection interruptions 
  52.      */  
  53.     public boolean acquire(long time, TimeUnit unit) throws Exception {  
  54.         return interProcessLock.acquire(time, unit);  
  55.     }  
  56.   
  57.     /** 
  58.      * Perform one release of the mutex. 
  59.      * 
  60.      * @throws Exception ZK errors, interruptions, current thread does not own the lock 
  61.      */  
  62.     public void release() throws Exception {  
  63.         interProcessLock.release();  
  64.     }  
  65.       
  66.     /** 
  67.      * Returns true if the mutex is acquired by a thread in this JVM 
  68.      * 
  69.      * @return true/false 
  70.      */  
  71.     public boolean isAcquiredInThisProcess() {  
  72.         return interProcessLock.isAcquiredInThisProcess();  
  73.     }  
  74. }  

 

到此代码已经完成。下面写一个简单的Demo:

Java代码   收藏代码
  1. //LockZookeeperClientFactory通常是通过Spring配置注入的,此处是为了Demo的简单明了才这样写的,不建议这样写  
  2. LockZookeeperClientFactory lzc = new LockZookeeperClientFactory();  
  3. lzc.setZookeeperIpPort("<span><span class="string">10.100.15.1</span></span>:8900");  
  4. lzc.setBasePath("/locks/sharedLock/");  
  5. lzc.init();  
  6.   
  7. SharedLock sharedLock = new SharedLock(lzc, "sharedLock1");  
  8. try {  
  9.     if (sharedLock.acquire(100, TimeUnit.MILLISECONDS)) {  
  10.         System.out.println("sharedLock1 get");  
  11.     }  
  12. catch (Exception e) {  
  13.     e.printStackTrace();  
  14. finally {  
  15.     try {  
  16.         sharedLock.release();  
  17.     } catch (Exception e) {  
  18.         e.printStackTrace();  
  19.     }  
  20. }  
  21.   
  22. lzc.destroy();  

 

就这样,系统就会每隔一分钟去回收一次没有使用的结点。

你可能感兴趣的:(基于Zookeeper的分布式共享锁)