一。为何使用分布式锁?
当应用服务器数量超过1台,对相同数据的访问可能造成访问冲突(特别是写冲突)。单纯使用关系数据库比如MYSQL的应用可以借助于事务来实现锁,也可以使用版本号等实现乐观锁,最大的缺陷就是可用性降低(性能差)。对于GLEASY这种满足大规模并发访问请求的应用来说,使用数据库事务来实现数据库就有些捉襟见肘了。另外对于一些不依赖数据库的应用,比如分布式文件系统,为了保证同一文件在大量读写操作情况下的正确性,必须引入分布式锁来约束对同一文件的并发操作。
二。对分布式锁的要求
1.高性能(分布式锁不能成为系统的性能瓶颈)
2.避免死锁(拿到锁的结点挂掉不会导致其它结点永远无法继续)
3.支持锁重入
三。方案1,基于zookeeper的分布式锁
001 |
/** |
002 |
* DistributedLockUtil.java |
003 |
* 分布式锁工厂类,所有分布式请求都由该工厂类负责 |
004 |
**/ |
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>(); |
011 |
|
012 |
private static Map<String,DistributedLockUtil> instances = new ConcurrentHashMap(); |
013 |
public static DistributedLockUtil getInstance(String schema){ |
014 |
DistributedLockUtil u = instances.get(schema); |
015 |
if (u== null ){ |
016 |
synchronized (schemeLock){ |
017 |
u = instances.get(schema); |
018 |
if (u == null ){ |
019 |
u = new DistributedLockUtil(schema); |
020 |
instances.put(schema, u); |
021 |
} |
022 |
} |
023 |
} |
024 |
return u; |
025 |
} |
026 |
|
027 |
private DistributedLockUtil(String schema){ |
028 |
this .schema = schema; |
029 |
} |
030 |
|
031 |
private Object getMutex(String key){ |
032 |
Object mx = mutexLockMap.get(key); |
033 |
if (mx == null ){ |
034 |
synchronized (mutexLock){ |
035 |
mx = mutexLockMap.get(key); |
036 |
if (mx== null ){ |
037 |
mx = new Object(); |
038 |
mutexLockMap.put(key,mx); |
039 |
} |
040 |
} |
041 |
} |
042 |
return mx; |
043 |
} |
044 |
|
045 |
private DistributedReentrantLock getLock(String key){ |
046 |
DistributedReentrantLock lock = cache.get(key); |
047 |
if (lock == null ){ |
048 |
synchronized (getMutex(key)){ |
049 |
lock = cache.get(key); |
050 |
if (lock == null ){ |
051 |
lock = new DistributedReentrantLock(key,schema); |
052 |
cache.put(key, lock); |
053 |
} |
054 |
} |
055 |
} |
056 |
return lock; |
057 |
} |
058 |
|
059 |
public void reset(){ |
060 |
for (String s : cache.keySet()){ |
061 |
getLock(s).unlock(); |
062 |
} |
063 |
} |
064 |
|
065 |
/** |
066 |
* 尝试加锁 |
067 |
* 如果当前线程已经拥有该锁的话,直接返回false,表示不用再次加锁,此时不应该再调用unlock进行解锁 |
068 |
* |
069 |
* @param key |
070 |
* @return |
071 |
* @throws InterruptedException |
072 |
* @throws KeeperException |
073 |
*/ |
074 |
public LockStat lock(String key) throws InterruptedException, KeeperException{ |
075 |
if (getLock(key).isOwner()){ |
076 |
return LockStat.NONEED; |
077 |
} |
078 |
getLock(key).lock(); |
079 |
return LockStat.SUCCESS; |
080 |
} |
081 |
|
082 |
public void clearLock(String key) throws InterruptedException, KeeperException{ |
083 |
synchronized (getMutex(key)){ |
084 |
DistributedReentrantLock l = cache.get(key); |
085 |
l.clear(); |
086 |
cache.remove(key); |
087 |
} |
088 |
} |
089 |
|
090 |
public void unlock(String key,LockStat stat) throws InterruptedException, KeeperException{ |
091 |
unlock(key,stat, false ); |
092 |
} |
093 |
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)){ |
101 |
lock.clear(); |
102 |
cache.remove(key); |
103 |
} |
104 |
} |
105 |
} |
106 |
} |
107 |
|
108 |
public static enum LockStat{ |
109 |
NONEED, |
110 |
SUCCESS |
111 |
} |
112 |
} |
001 |
/** |
002 |
*DistributedReentrantLock.java |
003 |
*本地线程之间锁争用,先使用虚拟机内部锁机制,减少结点间通信开销 |
004 |
*/ |
005 |
public class DistributedReentrantLock { |
006 |
private static final Logger logger = Logger.getLogger(DistributedReentrantLock. class ); |
007 |
private ReentrantLock reentrantLock = new ReentrantLock(); |
008 |
009 |
private WriteLock writeLock; |
010 |
private long timeout = 3 * 60 * 1000 ; |
011 |
|
012 |
private final Object mutex = new Object(); |
013 |
private String dir; |
014 |
private String schema; |
015 |
|
016 |
private final ExitListener exitListener = new ExitListener(){ |
017 |
@Override |
018 |
public void execute() { |
019 |
initWriteLock(); |
020 |
} |
021 |
}; |
022 |
|
023 |
private synchronized void initWriteLock(){ |
024 |
logger.debug( "初始化writeLock" ); |
025 |
writeLock = new WriteLock(dir, new LockListener(){ |
026 |
027 |
@Override |
028 |
public void lockAcquired() { |
029 |
synchronized (mutex){ |
030 |
mutex.notify(); |
031 |
} |
032 |
} |
033 |
@Override |
034 |
public void lockReleased() { |
035 |
} |
036 |
|
037 |
},schema); |
038 |
|
039 |
if (writeLock != null && writeLock.zk != null ){ |
040 |
writeLock.zk.addExitListener(exitListener); |
041 |
} |
042 |
|
043 |
synchronized (mutex){ |
044 |
mutex.notify(); |
045 |
} |
046 |
} |
047 |
|
048 |
public DistributedReentrantLock(String dir,String schema) { |
049 |
this .dir = dir; |
050 |
this .schema = schema; |
051 |
initWriteLock(); |
052 |
} |
053 |
054 |
public void lock( long timeout) throws InterruptedException, KeeperException { |
055 |
reentrantLock.lock(); //多线程竞争时,先拿到第一层锁 |
056 |
try { |
057 |
boolean res = writeLock.trylock(); |
058 |
if (!res){ |
059 |
synchronized (mutex){ |
060 |
mutex.wait(timeout); |
061 |
} |
062 |
if (writeLock == null || !writeLock.isOwner()){ |
063 |
throw new InterruptedException( "锁超时" ); |
064 |
} |
065 |
} |
066 |
} catch (InterruptedException e){ |
067 |
reentrantLock.unlock(); |
068 |
throw e; |
069 |
} catch (KeeperException e){ |
070 |
reentrantLock.unlock(); |
071 |
throw e; |
072 |
} |
073 |
} |
074 |
|
075 |
public void lock() throws InterruptedException, KeeperException { |
076 |
lock(timeout); |
077 |
} |
078 |
079 |
public void destroy() throws KeeperException { |
080 |
writeLock.unlock(); |
081 |
} |
082 |
|
083 |
084 |
public boolean unlock(){ |
085 |
if (!isOwner()) return false ; |
086 |
try { |
087 |
writeLock.unlock(); |
088 |
reentrantLock.unlock(); //多线程竞争时,释放最外层锁 |
089 |
} catch (RuntimeException e){ |
090 |
reentrantLock.unlock(); //多线程竞争时,释放最外层锁 |
091 |
throw e; |
092 |
} |
093 |
|
094 |
return reentrantLock.hasQueuedThreads(); |
095 |
} |
096 |
097 |
098 |
099 |
public boolean isOwner() { |
100 |
return reentrantLock.isHeldByCurrentThread() && writeLock.isOwner(); |
101 |
} |
102 |
103 |
public void clear() { |
104 |
writeLock.clear(); |
105 |
} |
106 |
107 |
} |
001 |
/** |
002 |
*WriteLock.java |
003 |
*基于zk的锁实现 |
004 |
*一个最简单的场景如下: |
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变更事件,判断出来自己已经是最小结点号,于是获得锁。 |
011 |
*/ |
012 |
public class WriteLock extends ZkPrimative { |
013 |
private static final Logger LOG = Logger.getLogger(WriteLock. class ); |
014 |
015 |
private final String dir; |
016 |
private String id; |
017 |
private LockNode idName; |
018 |
private String ownerId; |
019 |
private String lastChildId; |
020 |
private byte [] data = { 0x12 , 0x34 }; |
021 |
private LockListener callback; |
022 |
|
023 |
public WriteLock(String dir,String schema) { |
024 |
super (schema, true ); |
025 |
this .dir = dir; |
026 |
} |
027 |
|
028 |
public WriteLock(String dir,LockListener callback,String schema) { |
029 |
this (dir,schema); |
030 |
<a href= "http://www.nbso.ca/" >nbso online casino reviews</a> this .callback = callback; |
031 |
} |
032 |
033 |
public LockListener getLockListener() { |
034 |
return this .callback; |
035 |
} |
036 |
|
037 |
public void setLockListener(LockListener callback) { |
038 |
this .callback = callback; |
039 |
} |
040 |
041 |
public synchronized void unlock() throws RuntimeException { |
042 |
if (zk == null || zk.isClosed()){ |
043 |
return ; |
044 |
} |
045 |
if (id != null ) { |
046 |
try { |
047 |
zk.delete(id, - 1 ); |
048 |
} catch (InterruptedException e) { |
049 |
LOG.warn( "Caught: " e, e); |
050 |
//set that we have been interrupted. |
051 |
Thread.currentThread().interrupt(); |
052 |
} catch (KeeperException.NoNodeException e) { |
053 |
// do nothing |
054 |
} catch (KeeperException e) { |
055 |
LOG.warn( "Caught: " e, e); |
056 |
throw (RuntimeException) new RuntimeException(e.getMessage()). |
057 |
initCause(e); |
058 |
} finally { |
059 |
if (callback != null ) { |
060 |
callback.lockReleased(); |
061 |
} |
062 |
id = null ; |
063 |
} |
064 |
} |
065 |
} |
066 |
|
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()); |
071 |
try { |
072 |
trylock(); |
073 |
} catch (Exception e) { |
074 |
LOG.warn( "Failed to acquire lock: " e, e); |
075 |
} |
076 |
} |
077 |
} |
078 |
|
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)) { |
084 |
id = dir "/" name; |
085 |
if (LOG.isDebugEnabled()) { |
086 |
LOG.debug( "Found id created last time: " id); |
087 |
} |
088 |
break ; |
089 |
} |
090 |
} |
091 |
if (id == null ) { |
092 |
id = zookeeper.create(dir "/" prefix, data, |
093 |
acl, EPHEMERAL_SEQUENTIAL); |
094 |
095 |
if (LOG.isDebugEnabled()) { |
096 |
LOG.debug( "Created id: " id); |
097 |
} |
098 |
} |
099 |
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,性能比较一般;