導言:
前段時間項目中用到了分布式鎖,所以就對分布式鎖進行了一些研究,首先當然是去看redis的分布式鎖實現,這里說明一下,分布式鎖的實現可以有兩種比較簡單的方式來實現,一種是redis的方式來實現,一種是使用zookeeper的方式來實現。
redis的優點是性能更高,因為是純內存的存儲,而且原生單線程的模型不存在并發的一些問題,所以不需要額外的開銷來保證命令的原子執行。zookeeper的話優點是更穩定,但是缺點也很明顯,zookeeper是一種文件系統,且主要是用來做分布式一致性工作的,所以效率上來說是比不上redis的;
實現:
1.redis的實現需要參考一種比較成熟的分布式鎖算法,步驟如下(假設使用的是jedis客戶端,其他客戶端同理)
public static long getDistibuteLock(JedisCluster jedisCluster,String key,long expireTime) {
if (jedisCluster == null || Strings.isNullOrEmpty(key) || expireTime < 1) {
return -1L;
}
//首先嘗試去設置過期時間,該命令只有在這個key不存在時才能成功
long nowValue = Instant.now().toEpochMilli() + expireTime;
Long result = jedisCluster.setnx(key,String.valueOf(nowValue));
if (result > 0) {
return expireTime;
}
//獲取redis上的過期時間,和本地時間戳做比較,只有本地時間大于過期時間戳,才會去執行更新
//如果是空值,則說明鎖已經被釋放,這個時候應該再次去獲取鎖
String value = jedisCluster.get(key);
if (Strings.isNullOrEmpty(value)) {
nowValue = Instant.now().toEpochMilli() + expireTime;
result = jedisCluster.setnx(key,String.valueOf(nowValue));
//獲取成功則返回新的過期時間戳,失敗直接返回0;
if (result > 1) {
return nowValue;
}else {
return 0L;
}//如果返回不為空,且時間戳小于本地時間戳,則說明該分布式鎖已經過期了,可以替換
}else if (Long.valueOf(value) < Instant.now().toEpochMilli()){
nowValue = Instant.now().toEpochMilli() + expireTime;
String oldValue = jedisCluster.getSet(key,String.valueOf(nowValue));
if (Strings.isNullOrEmpty(oldValue) || value.equals(oldValue)) {
return nowValue;//這里的關鍵是注意一定要判斷getset后返回的值,如果為空或者和之前取到的值相等,則說明拿到了鎖;反之則說明是別的拿到了鎖;
}else {
return 0L;
}
}else {
return 0L;
}
}
2.解鎖操作需要進行驗證
public static void releaseLock(JedisCluster jedisCluster,String key,long oldTime) {
if (jedisCluster == null || Strings.isNullOrEmpty(key) || oldTime < 1) {
return;
}
//首先判斷該鎖是否存在,如果不存在直接返回
String remoteValue = jedisCluster.get(key);
if (Strings.isNullOrEmpty(key)) {
return;
}
//關鍵是需要知道是否是自己設置的時間,如果不是的話那么就說明不是自己的鎖,不做處理直接返回
try {
long time = Long.parseLong(remoteValue);
if (time == oldTime) {
jedisCluster.del(key);
}
}catch (NumberFormatException e) {
return;
}
}
這種解決思路是在redis單點的情況下比較簡單的一種進行加鎖的方式,當然缺點也很明顯,如果正好處于master掛掉,而slave機器并沒有完全同步的情況下,會有其他進程獲取鎖,另外也沒有辦法避免當獲取鎖的jvm掛了以后,這個鎖會持續一段時間無法被清理;甚至是獲取鎖的jvm一旦阻塞有可能會是多個jvm獲取鎖;