一。为何使用分布式锁?
当应用服务器数量超过1台,对相同数据的访问可能造成访问冲突(特别是写冲突)。单纯使用关系数据库比如MYSQL的应用可以借助于事务来实现锁,也可以使用版本号等实现乐观锁,最大的缺陷就是可用性降低(性能差)。对于GLEASY这种满足大规模并发访问请求的应用来说,使用数据库事务来实现数据库就有些捉襟见肘了。另外对于一些不依赖数据库的应用,比如分布式文件系统,为了保证同一文件在大量读写操作情况下的正确性,必须引入分布式锁来约束对同一文件的并发操作。
二。对分布式锁的要求
1.高性能(分布式锁不能成为系统的性能瓶颈)
2.避免死锁(拿到锁的结点挂掉不会导致其它结点永远无法继续)
3.支持锁重入
三。方案1,基于zookeeper的分布式锁
002 | * DistributedLockUtil.java |
003 | * 分布式锁工厂类,所有分布式请求都由该工厂类负责 |
005 | public class DistributedLockUtil { |
006 | private static Object schemeLock = new Object(); |
007 | private static Object mutexLock = new Object(); |
008 | private static Map<String,Object> mutexLockMap = new ConcurrentHashMap(); |
009 | private String schema; |
010 | private Map<String,DistributedReentrantLock> cache = new ConcurrentHashMap<String,DistributedReentrantLock>(); |
012 | private static Map<String,DistributedLockUtil> instances = new ConcurrentHashMap(); |
013 | public static DistributedLockUtil getInstance(String schema){ |
014 | DistributedLockUtil u = instances.get(schema); |
016 | synchronized (schemeLock){ |
017 | u = instances.get(schema); |
019 | u = new DistributedLockUtil(schema); |
020 | instances.put(schema, u); |
027 | private DistributedLockUtil(String schema){ |
028 | this .schema = schema; |
031 | private Object getMutex(String key){ |
032 | Object mx = mutexLockMap.get(key); |
034 | synchronized (mutexLock){ |
035 | mx = mutexLockMap.get(key); |
038 | mutexLockMap.put(key,mx); |
045 | private DistributedReentrantLock getLock(String key){ |
046 | DistributedReentrantLock lock = cache.get(key); |
048 | synchronized (getMutex(key)){ |
049 | lock = cache.get(key); |
051 | lock = new DistributedReentrantLock(key,schema); |
052 | cache.put(key, lock); |
060 | for (String s : cache.keySet()){ |
067 | * 如果当前线程已经拥有该锁的话,直接返回false,表示不用再次加锁,此时不应该再调用unlock进行解锁 |
071 | * @throws InterruptedException |
072 | * @throws KeeperException |
074 | public LockStat lock(String key) throws InterruptedException, KeeperException{ |
075 | if (getLock(key).isOwner()){ |
076 | return LockStat.NONEED; |
079 | return LockStat.SUCCESS; |
082 | public void clearLock(String key) throws InterruptedException, KeeperException{ |
083 | synchronized (getMutex(key)){ |
084 | DistributedReentrantLock l = cache.get(key); |
090 | public void unlock(String key,LockStat stat) throws InterruptedException, KeeperException{ |
091 | unlock(key,stat, false ); |
094 | public void unlock(String key,LockStat stat, boolean keepalive) throws InterruptedException, KeeperException{ |
095 | if (stat == null ) return ; |
096 | if (LockStat.SUCCESS.equals(stat)){ |
097 | DistributedReentrantLock lock = getLock(key); |
098 | boolean hasWaiter = lock.unlock(); |
099 | if (!hasWaiter && !keepalive){ |
100 | synchronized (getMutex(key)){ |
108 | public static enum LockStat{ |
002 | *DistributedReentrantLock.java |
003 | *本地线程之间锁争用,先使用虚拟机内部锁机制,减少结点间通信开销 |
005 | public class DistributedReentrantLock { |
006 | private static final Logger logger = Logger.getLogger(DistributedReentrantLock. class ); |
007 | private ReentrantLock reentrantLock = new ReentrantLock(); |
009 | private WriteLock writeLock; |
010 | private long timeout = 3 * 60 * 1000 ; |
012 | private final Object mutex = new Object(); |
014 | private String schema; |
016 | private final ExitListener exitListener = new ExitListener(){ |
018 | public void execute() { |
023 | private synchronized void initWriteLock(){ |
024 | logger.debug( "初始化writeLock" ); |
025 | writeLock = new WriteLock(dir, new LockListener(){ |
028 | public void lockAcquired() { |
034 | public void lockReleased() { |
039 | if (writeLock != null && writeLock.zk != null ){ |
040 | writeLock.zk.addExitListener(exitListener); |
048 | public DistributedReentrantLock(String dir,String schema) { |
050 | this .schema = schema; |
054 | public void lock( long timeout) throws InterruptedException, KeeperException { |
055 | reentrantLock.lock(); |
057 | boolean res = writeLock.trylock(); |
062 | if (writeLock == null || !writeLock.isOwner()){ |
063 | throw new InterruptedException( "锁超时" ); |
066 | } catch (InterruptedException e){ |
067 | reentrantLock.unlock(); |
069 | } catch (KeeperException e){ |
070 | reentrantLock.unlock(); |
075 | public void lock() throws InterruptedException, KeeperException { |
079 | public void destroy() throws KeeperException { |
084 | public boolean unlock(){ |
085 | if (!isOwner()) return false ; |
088 | reentrantLock.unlock(); |
089 | } catch (RuntimeException e){ |
090 | reentrantLock.unlock(); |
094 | return reentrantLock.hasQueuedThreads(); |
099 | public boolean isOwner() { |
100 | return reentrantLock.isHeldByCurrentThread() && writeLock.isOwner(); |
103 | public void clear() { |
005 | *1.结点A请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号1 |
006 | *2.结点B请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号2 |
007 | *3.结点A获取所有结点ID,判断出来自己是最小结点号,于是获得锁 |
008 | *4.结点B获取所有结点ID,判断出来自己不是最小结点,于是监听小于自己的最大结点(结点A)变更事件 |
009 | *5.结点A拿到锁,处理业务,处理完,释放锁(删除自己) |
010 | *6.结点B收到结点A变更事件,判断出来自己已经是最小结点号,于是获得锁。 |
012 | public class WriteLock extends ZkPrimative { |
013 | private static final Logger LOG = Logger.getLogger(WriteLock. class ); |
015 | private final String dir; |
017 | private LockNode idName; |
018 | private String ownerId; |
019 | private String lastChildId; |
020 | private byte [] data = { 0x12 , 0x34 }; |
021 | private LockListener callback; |
023 | public WriteLock(String dir,String schema) { |
028 | public WriteLock(String dir,LockListener callback,String schema) { |
033 | public LockListener getLockListener() { |
034 | return this .callback; |
037 | public void setLockListener(LockListener callback) { |
038 | this .callback = callback; |
041 | public synchronized void unlock() throws RuntimeException { |
042 | if (zk == null || zk.isClosed()){ |
048 | } catch (InterruptedException e) { |
049 | LOG.warn( "Caught: " e, e); |
051 | Thread.currentThread().interrupt(); |
052 | } catch (KeeperException.NoNodeException e) { |
054 | } catch (KeeperException e) { |
055 | LOG.warn( "Caught: " e, e); |
056 | throw (RuntimeException) new RuntimeException(e.getMessage()). |
059 | if (callback != null ) { |
060 | callback.lockReleased(); |
067 | private class LockWatcher implements Watcher { |
068 | public void process(WatchedEvent event) { |
069 | LOG.debug( "Watcher fired on path: " event.getPath() " state: " |
070 | event.getState() " type " event.getType()); |
073 | } catch (Exception e) { |
074 | LOG.warn( "Failed to acquire lock: " e, e); |
079 | private void findPrefixInChildren(String prefix, ZooKeeper zookeeper, String dir) |
080 | throws KeeperException, InterruptedException { |
081 | List<String> names = zookeeper.getChildren(dir, false ); |
082 | for (String name : names) { |
083 | if (name.startsWith(prefix)) { |
085 | if (LOG.isDebugEnabled()) { |
086 | LOG.debug( "Found id created last time: " id); |
092 | id = zookeeper.create(dir "/" prefix, data, |
093 | acl, EPHEMERAL_SEQUENTIAL); |
095 | if (LOG.isDebugEnabled()) { |
096 | LOG.debug( "Created id: " id); |
102 | public void clear() { |
103 | if (zk == null || zk.isClosed()){ |
108 | } catch (Exception e) { |
109 | LOG.error( "clear error: " e,e); |
113 | public synchronized boolean trylock() throws KeeperException, InterruptedException { |
122 | ensurePathExists(dir); |
127 | long sessionId = zk.getSessionId(); |
128 | String prefix = "x-" sessionId "-" ; |
129 | idName = new LockNode(id); |
130 | LOG.debug( "idName:" idName); |
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..." ); |
139 | SortedSet<LockNode> sortedNames = new TreeSet<LockNode>(); |
140 | for (String name : names) { |
141 | sortedNames.add( new LockNode(dir "/" name)); |
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); |
153 | Stat stat = zk.exists(lastChildId, new LockWatcher()); |
155 | return Boolean.FALSE; |
157 | LOG.warn( "Could not find the" |
158 | " stats for less than me: " lastChildName.getName()); |
162 | if (callback != null ) { |
163 | callback.lockAcquired(); |
172 | return Boolean.FALSE; |
175 | public String getDir() { |
179 | public boolean isOwner() { |
180 | return id != null && ownerId != null && id.equals(ownerId); |
183 | public String getId() { |
使用本方案实现的分布式锁,可以很好地解决锁重入的问题,而且使用会话结点来避免死锁;性能方面,根据笔者自测结果,加锁解锁各一次算是一个操作,本方案实现的分布式锁,TPS大概为2000-3000,性能比较一般;