1.基于Redis單點的分布式鎖
1.1 加鎖
- Redis命令
#my_random_value是由客戶端生成的一個隨機字符串,它要保證在足夠長的一段時間內(nèi)在所有客戶端的所有獲取鎖的請求中都是唯一的。
#NX表示只有當resource_name對應(yīng)的key值不存在的時候才能SET成功。這保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。
#PX 30000表示這個鎖有一個30秒的自動過期時間。當然,這里30秒只是一個例子,客戶端可以選擇合適的過期時間。
SET resource_name my_random_value NX PX 30000
- Java 版本
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 嘗試獲取分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @param expireTime 超期時間
* @return 是否獲取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
/*
* 第一個為key,我們使用key來當鎖,因為key是唯一的。
* 第二個為value,我們傳的是requestId,很多童鞋可能不明白,有key作為鎖不就夠了嗎,為什么還要用到value?原因就是我們在上面講到可靠性時,分布式鎖要滿足第四個條件解鈴還須系鈴人,通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據(jù)。requestId可以使用UUID.randomUUID().toString()方法生成。
* 第三個為nxxx,這個參數(shù)我們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經(jīng)存在,則不做任何操作;
* 第四個為expx,這個參數(shù)我們傳的是PX,意思是我們要給這個key加一個過期的設(shè)置,具體時間由第五個參數(shù)決定。
* 第五個為time,與第四個參數(shù)相呼應(yīng),代表key的過期時間。
*/
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
1.2 釋放鎖
- Redis命令:Redis Lua腳本
#這段Lua腳本在執(zhí)行的時候要把前面的my_random_value作為ARGV[1]的值傳進去,把resource_name作為KEYS[1]的值傳進去。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
- Java版本
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 釋放分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @return 是否釋放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
/*
*將Lua代碼傳到j(luò)edis.eval()方法里,并使參數(shù)KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。eval()方法是將Lua代碼交給Redis服務(wù)端執(zhí)行。
*/
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
2.基于集群的分布式鎖
使用Redis提供的RedLock,具體可以參考官方文檔
3.參考資料
Martin 與 antirez 的討論
基于Redis的分布式鎖到底安全嗎(上)
基于Redis的分布式鎖到底安全嗎(下)
Redis單機實現(xiàn)分布式鎖分析
Redis 分布式鎖的正確實現(xiàn)方式( Java 版 )