两种分布式锁实现方案(一)

来源:互联网 发布:js时间控件webdriver 编辑:程序博客网 时间:2024/05/21 22:20

一。为何使用分布式锁?
当应用服务器数量超过1台,对相同数据的访问可能造成访问冲突(特别是写冲突)。单纯使用关系数据库比如MySQL的应用可以借助于事务来实现锁,也可以使用版本号等实现乐观锁,最大的缺陷就是可用性降低(性能差)。对于GLEASY这种满足大规模并发访问请求的应用来说,使用数据库事务来实现数据库就有些捉襟见肘了。另外对于一些不依赖数据库的应用,比如分布式文件系统,为了保证同一文件在大量读写操作情况下的正确性,必须引入分布式锁来约束对同一文件的并发操作。

二。对分布式锁的要求
1.高性能(分布式锁不能成为系统的性能瓶颈)
2.避免死锁(拿到锁的结点挂掉不会导致其它结点永远无法继续)
3.支持锁重入

三。方案1,基于zookeeper的分布式锁

[java] view plain copy
  1. /** 
  2.  * DistributedLockUtil.java 
  3.  * 分布式锁工厂类,所有分布式请求都由该工厂类负责 
  4.  */  
  5. public class DistributedLockUtil {  
  6.     private static Object schemeLock = new Object();  
  7.     private static Object mutexLock = new Object();  
  8.     private static Map<String, Object> mutexLockMap = new ConcurrentHashMap();  
  9.     private String schema;  
  10.     private Map<String, DistributedReentrantLock> cache = new ConcurrentHashMap<String, DistributedReentrantLock>();  
  11.   
  12.     private static Map<String, DistributedLockUtil> instances = new ConcurrentHashMap();  
  13.   
  14.     public static DistributedLockUtil getInstance(String schema) {  
  15.         DistributedLockUtil u = instances.get(schema);  
  16.         if (u == null) {  
  17.             synchronized (schemeLock) {  
  18.                 u = instances.get(schema);  
  19.                 if (u == null) {  
  20.                     u = new DistributedLockUtil(schema);  
  21.                     instances.put(schema, u);  
  22.                 }  
  23.             }  
  24.         }  
  25.         return u;  
  26.     }  
  27.   
  28.     private DistributedLockUtil(String schema) {  
  29.         this.schema = schema;  
  30.     }  
  31.   
  32.     private Object getMutex(String key) {  
  33.         Object mx = mutexLockMap.get(key);  
  34.         if (mx == null) {  
  35.             synchronized (mutexLock) {  
  36.                 mx = mutexLockMap.get(key);  
  37.                 if (mx == null) {  
  38.                     mx = new Object();  
  39.                     mutexLockMap.put(key, mx);  
  40.                 }  
  41.             }  
  42.         }  
  43.         return mx;  
  44.     }  
  45.   
  46.     private DistributedReentrantLock getLock(String key) {  
  47.         DistributedReentrantLock lock = cache.get(key);  
  48.         if (lock == null) {  
  49.             synchronized (getMutex(key)) {  
  50.                 lock = cache.get(key);  
  51.                 if (lock == null) {  
  52.                     lock = new DistributedReentrantLock(key, schema);  
  53.                     cache.put(key, lock);  
  54.                 }  
  55.             }  
  56.         }  
  57.         return lock;  
  58.     }  
  59.   
  60.     public void reset() {  
  61.         for (String s : cache.keySet()) {  
  62.             getLock(s).unlock();  
  63.         }  
  64.     }  
  65.   
  66.     /** 
  67.      * 尝试加锁 
  68.      * 如果当前线程已经拥有该锁的话,直接返回false,表示不用再次加锁,此时不应该再调用unlock进行解锁 
  69.      * 
  70.      * @param key 
  71.      * @return 
  72.      * @throws InterruptedException 
  73.      * @throws KeeperException 
  74.      */  
  75.     public LockStat lock(String key) throws InterruptedException, KeeperException {  
  76.         if (getLock(key).isOwner()) {  
  77.             return LockStat.NONEED;  
  78.         }  
  79.         getLock(key).lock();  
  80.         return LockStat.SUCCESS;  
  81.     }  
  82.   
  83.     public void clearLock(String key) throws InterruptedException, KeeperException {  
  84.         synchronized (getMutex(key)) {  
  85.             DistributedReentrantLock l = cache.get(key);  
  86.             l.clear();  
  87.             cache.remove(key);  
  88.         }  
  89.     }  
  90.   
  91.     public void unlock(String key, LockStat stat) throws InterruptedException, KeeperException {  
  92.         unlock(key, stat, false);  
  93.     }  
  94.   
  95.     public void unlock(String key, LockStat stat, boolean keepalive) throws InterruptedException, KeeperException {  
  96.         if (stat == nullreturn;  
  97.         if (LockStat.SUCCESS.equals(stat)) {  
  98.             DistributedReentrantLock lock = getLock(key);  
  99.             boolean hasWaiter = lock.unlock();  
  100.             if (!hasWaiter && !keepalive) {  
  101.                 synchronized (getMutex(key)) {  
  102.                     lock.clear();  
  103.                     cache.remove(key);  
  104.                 }  
  105.             }  
  106.         }  
  107.     }  
  108.   
  109.     public static enum LockStat {  
  110.         NONEED,  
  111.         SUCCESS  
  112.     }  
  113. }  

[java] view plain copy
  1. /** 
  2.  * DistributedReentrantLock.java 
  3.  * 本地线程之间锁争用,先使用虚拟机内部锁机制,减少结点间通信开销 
  4.  */  
  5. public class DistributedReentrantLock {  
  6.     private static final Logger logger = Logger.getLogger(DistributedReentrantLock.class);  
  7.     private ReentrantLock reentrantLock = new ReentrantLock();  
  8.   
  9.     private WriteLock writeLock;  
  10.     private long timeout = 3 * 60 * 1000;  
  11.   
  12.     private final Object mutex = new Object();  
  13.     private String dir;  
  14.     private String schema;  
  15.   
  16.     private final ExitListener exitListener = new ExitListener() {  
  17.         @Override  
  18.         public void execute() {  
  19.             initWriteLock();  
  20.         }  
  21.     };  
  22.   
  23.     private synchronized void initWriteLock() {  
  24.         logger.debug("初始化writeLock");  
  25.         writeLock = new WriteLock(dir, new LockListener() {  
  26.   
  27.             @Override  
  28.             public void lockAcquired() {  
  29.                 synchronized (mutex) {  
  30.                     mutex.notify();  
  31.                 }  
  32.             }  
  33.   
  34.             @Override  
  35.             public void lockReleased() {  
  36.             }  
  37.   
  38.         }, schema);  
  39.   
  40.         if (writeLock != null && writeLock.zk != null) {  
  41.             writeLock.zk.addExitListener(exitListener);  
  42.         }  
  43.   
  44.         synchronized (mutex) {  
  45.             mutex.notify();  
  46.         }  
  47.     }  
  48.   
  49.     public DistributedReentrantLock(String dir, String schema) {  
  50.         this.dir = dir;  
  51.         this.schema = schema;  
  52.         initWriteLock();  
  53.     }  
  54.   
  55.     public void lock(long timeout) throws InterruptedException, KeeperException {  
  56.         reentrantLock.lock();//多线程竞争时,先拿到第一层锁  
  57.         try {  
  58.             boolean res = writeLock.trylock();  
  59.             if (!res) {  
  60.                 synchronized (mutex) {  
  61.                     mutex.wait(timeout);  
  62.                 }  
  63.                 if (writeLock == null || !writeLock.isOwner()) {  
  64.                     throw new InterruptedException("锁超时");  
  65.                 }  
  66.             }  
  67.         } catch (InterruptedException e) {  
  68.             reentrantLock.unlock();  
  69.             throw e;  
  70.         } catch (KeeperException e) {  
  71.             reentrantLock.unlock();  
  72.             throw e;  
  73.         }  
  74.     }  
  75.   
  76.     public void lock() throws InterruptedException, KeeperException {  
  77.         lock(timeout);  
  78.     }  
  79.   
  80.     public void destroy() throws KeeperException {  
  81.         writeLock.unlock();  
  82.     }  
  83.   
  84.   
  85.     public boolean unlock() {  
  86.         if (!isOwner()) return false;  
  87.         try {  
  88.             writeLock.unlock();  
  89.             reentrantLock.unlock();//多线程竞争时,释放最外层锁  
  90.         } catch (RuntimeException e) {  
  91.             reentrantLock.unlock();//多线程竞争时,释放最外层锁  
  92.             throw e;  
  93.         }  
  94.   
  95.         return reentrantLock.hasQueuedThreads();  
  96.     }  
  97.   
  98.   
  99.     public boolean isOwner() {  
  100.         return reentrantLock.isHeldByCurrentThread() && writeLock.isOwner();  
  101.     }  
  102.   
  103.     public void clear() {  
  104.         writeLock.clear();  
  105.     }  
  106.   
  107. }  

[java] view plain copy
  1. /** 
  2.  * WriteLock.java 
  3.  * 基于zk的锁实现 
  4.  * 一个最简单的场景如下: 
  5.  * 1.结点A请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号1 
  6.  * 2.结点B请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号2 
  7.  * 3.结点A获取所有结点ID,判断出来自己是最小结点号,于是获得锁 
  8.  * 4.结点B获取所有结点ID,判断出来自己不是最小结点,于是监听小于自己的最大结点(结点A)变更事件 
  9.  * 5.结点A拿到锁,处理业务,处理完,释放锁(删除自己) 
  10.  * 6.结点B收到结点A变更事件,判断出来自己已经是最小结点号,于是获得锁。 
  11.  */  
  12. public class WriteLock extends ZkPrimative {  
  13.     private static final Logger LOG = Logger.getLogger(WriteLock.class);  
  14.   
  15.     private final String dir;  
  16.     private String id;  
  17.     private LockNode idName;  
  18.     private String ownerId;  
  19.     private String lastChildId;  
  20.     private byte[] data = {0x120x34};  
  21.     private LockListener callback;  
  22.   
  23.     public WriteLock(String dir, String schema) {  
  24.         super(schema, true);  
  25.         this.dir = dir;  
  26.     }  
  27.   
  28.     public WriteLock(String dir, LockListener callback, String schema) {  
  29.         this(dir, schema);  
  30.         <a href = "http://www.nbso.ca/" > nbso online casino reviews</a > this.callback = callback;  
  31.     }  
  32.   
  33.     public LockListener getLockListener() {  
  34.         return this.callback;  
  35.     }  
  36.   
  37.     public void setLockListener(LockListener callback) {  
  38.         this.callback = callback;  
  39.     }  
  40.   
  41.     public synchronized void unlock() throws RuntimeException {  
  42.         if (zk == null || zk.isClosed()) {  
  43.             return;  
  44.         }  
  45.         if (id != null) {  
  46.             try {  
  47.                 zk.delete(id, -1);  
  48.             } catch (InterruptedException e) {  
  49.                 LOG.warn("Caught: "e, e);  
  50.                 //set that we have been interrupted.  
  51.                 Thread.currentThread().interrupt();  
  52.             } catch (KeeperException.NoNodeException e) {  
  53.                 // do nothing  
  54.             } catch (KeeperException e) {  
  55.                 LOG.warn("Caught: "e, e);  
  56.                 throw (RuntimeException) new RuntimeException(e.getMessage()).  
  57.                         initCause(e);  
  58.             } finally {  
  59.                 if (callback != null) {  
  60.                     callback.lockReleased();  
  61.                 }  
  62.                 id = null;  
  63.             }  
  64.         }  
  65.     }  
  66.   
  67.     private class LockWatcher implements Watcher {  
  68.         public void process(WatchedEvent event) {  
  69.             LOG.debug("Watcher fired on path: "event.getPath()" state: "  
  70.                     event.getState()" type "event.getType());  
  71.             try {  
  72.                 trylock();  
  73.             } catch (Exception e) {  
  74.                 LOG.warn("Failed to acquire lock: "e, e);  
  75.             }  
  76.         }  
  77.     }  
  78.   
  79.     private void findPrefixInChildren(String prefix, ZooKeeper zookeeper, String dir)  
  80.             throws KeeperException, InterruptedException {  
  81.         List<String> names = zookeeper.getChildren(dir, false);  
  82.         for (String name : names) {  
  83.             if (name.startsWith(prefix)) {  
  84.                 id = dir "/" name;  
  85.                 if (LOG.isDebugEnabled()) {  
  86.                     LOG.debug("Found id created last time: "id);  
  87.                 }  
  88.                 break;  
  89.             }  
  90.         }  
  91.         if (id == null) {  
  92.             id = zookeeper.create(dir"/"prefix, data,  
  93.                     acl, EPHEMERAL_SEQUENTIAL);  
  94.   
  95.             if (LOG.isDebugEnabled()) {  
  96.                 LOG.debug("Created id: "id);  
  97.             }  
  98.         }  
  99.   
  100.     }  
  101.   
  102.     public void clear() {  
  103.         if (zk == null || zk.isClosed()) {  
  104.             return;  
  105.         }  
  106.         try {  
  107.             zk.delete(dir, -1);  
  108.         } catch (Exception e) {  
  109.             LOG.error("clear error: "e, e);  
  110.         }  
  111.     }  
  112.   
  113.     public synchronized boolean trylock() throws KeeperException, InterruptedException {  
  114.         if (zk == null) {  
  115.             LOG.info("zk 是空");  
  116.             return false;  
  117.         }  
  118.         if (zk.isClosed()) {  
  119.             LOG.info("zk 已经关闭");  
  120.             return false;  
  121.         }  
  122.         ensurePathExists(dir);  
  123.   
  124.         LOG.debug("id:"id);  
  125.         do {  
  126.             if (id == null) {  
  127.                 long sessionId = zk.getSessionId();  
  128.                 String prefix = "x-" sessionId "-";  
  129.                 idName = new LockNode(id);  
  130.                 LOG.debug("idName:"idName);  
  131.             }  
  132.             if (id != null) {  
  133.                 List<String> names = zk.getChildren(dir, false);  
  134.                 if (names.isEmpty()) {  
  135.                     LOG.warn("No children in: "dir" when we've just "  
  136.                             "created one! Lets recreate it...");  
  137.                     id = null;  
  138.                 } else {  
  139.                     SortedSet<LockNode> sortedNames = new TreeSet<LockNode>();  
  140.                     for (String name : names) {  
  141.                         sortedNames.add(new LockNode(dir"/"name));  
  142.                     }  
  143.                     ownerId = sortedNames.first().getName();  
  144.                     LOG.debug("all:"sortedNames);  
  145.                     SortedSet<LockNode> lessThanMe = sortedNames.headSet(idName);  
  146.                     LOG.debug("less than me:"lessThanMe);  
  147.                     if (!lessThanMe.isEmpty()) {  
  148.                         LockNode lastChildName = lessThanMe.last();  
  149.                         lastChildId = lastChildName.getName();  
  150.                         if (LOG.isDebugEnabled()) {  
  151.                             LOG.debug("watching less than me node: "lastChildId);  
  152.                         }  
  153.                         Stat stat = zk.exists(lastChildId, new LockWatcher());  
  154.                         if (stat != null) {  
  155.                             return Boolean.FALSE;  
  156.                         } else {  
  157.                             LOG.warn("Could not find the"  
  158.                                     " stats for less than me: "lastChildName.getName());  
  159.                         }  
  160.                     } else {  
  161.                         if (isOwner()) {  
  162.                             if (callback != null) {  
  163.                                 callback.lockAcquired();  
  164.                             }  
  165.                             return Boolean.TRUE;  
  166.                         }  
  167.                     }  
  168.                 }  
  169.             }  
  170.         }  
  171.         while (id == null);  
  172.         return Boolean.FALSE;  
  173.     }  
  174.   
  175.     public String getDir() {  
  176.         return dir;  
  177.     }  
  178.   
  179.     public boolean isOwner() {  
  180.         return id != null && ownerId != null && id.equals(ownerId);  
  181.     }  
  182.   
  183.     public String getId() {  
  184.         return this.id;  
  185.     }  
  186. }  


使用本方案实现的分布式锁,可以很好地解决锁重入的问题,而且使用会话结点来避免死锁;性能方面,根据笔者自测结果,加锁解锁各一次算是一个操作,本方案实现的分布式锁,TPS大概为2000-3000,性能比较一般;

原创粉丝点击