前言
最近在工作中使用到了分布式鎖鬓梅,所以在本文中記錄一下如何在Java中使用Redis實(shí)現(xiàn)分布式鎖
四個(gè)特性
在使用分布式鎖的時(shí)候,我們需要滿足以下四個(gè)條件:
互斥性
在同一時(shí)間只能有一個(gè)客戶端持有鎖拦耐。容錯(cuò)性
客戶端可以實(shí)現(xiàn)加鎖和解鎖臣咖。可靠性
即使在客戶端崩潰后蕊苗,無(wú)法主動(dòng)釋放鎖的情況下馁龟, 也可以保證后續(xù)客戶端持有該鎖想罕。一致性
加鎖和解鎖需是同一客戶端,必須避免當(dāng)前客戶端的鎖被其他客戶端解鎖。
具體實(shí)現(xiàn)
加鎖
直接上代碼粉洼,加鎖其實(shí)并不復(fù)雜。
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static final String LOCK_SUCCESS = "OK";
private static final String LOCK_KEY = "redisLockTest";
public void test() {
Jedis jedis = getJedis();
String uuid = UUID.randomUUID().toString();
threadLocal.set(uuid);
boolean result = tryLock(jedis,LOCK_KEY,uuid,10);
if (result) {
//......具體業(yè)務(wù)實(shí)現(xiàn)
}
}
/**
* 獲取分布式鎖
*
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @param expireTime 過(guò)期時(shí)間
* @return 是否獲取成功
*
*/
public static boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
具體核心代碼其實(shí)就是這一行:
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
第一個(gè)參數(shù)為Key叶摄,鎖的名稱属韧,并且是全局唯一。
第二個(gè)參數(shù)為value蛤吓,這里是使用的一串隨機(jī)字符宵喂,存入當(dāng)前線程本地里面。value具體的作用是要在解鎖的時(shí)候柱衔,判斷加鎖解鎖是否為一個(gè)客戶端樊破。
第三個(gè)參數(shù)為“NX” ,意思是SET IF NOT EXIST唆铐,即當(dāng)key不存在時(shí)哲戚,我們進(jìn)行set操作;若key已經(jīng)存在艾岂,則不做任何操作顺少。
第四個(gè)參數(shù)為"PX",這個(gè)參數(shù)是過(guò)期時(shí)間單位,意思是我們要給這個(gè)key加一個(gè)過(guò)期時(shí)間脆炎,如果客戶端在操作的時(shí)候突然宕機(jī)梅猿,Redis在過(guò)期后進(jìn)行自動(dòng)刪除,避免長(zhǎng)時(shí)間持有鎖造成死鎖秒裕。
第五個(gè)參數(shù)為expireTime袱蚓,具體的過(guò)期時(shí)間。
解鎖
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static final String LOCK_SUCCESS = "OK";
private static final String LOCK_KEY = "redisLockTest";
private static final Long RELEASE_SUCCESS = 1L;
public void test() {
try {
Jedis jedis = getJedis();
boolean result = releaseLock(jedis, LOCK_KEY, threadLocal.get());
if (result) {
//......具體業(yè)務(wù)實(shí)現(xiàn)
}
}finally {
threadLocal.remove();
}
}
/**
* 釋放分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @return 是否釋放成功
*/
public static boolean releaseLock(Jedis jedis, String lockKey, String requestId) {
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;
}
對(duì)于解鎖來(lái)說(shuō)几蜻,我們需要使用lua腳本來(lái)實(shí)現(xiàn)解鎖喇潘,因?yàn)槲覀冃枰诮怄i時(shí)判斷當(dāng)前鎖是否是當(dāng)前客戶端所持有,所以我們要先進(jìn)行g(shù)et判斷key和value是否一致梭稚,如果一致我們進(jìn)行刪除颖低。這個(gè)地方我們不能使用平常的redis命令來(lái)進(jìn)行操作,因?yàn)槲覀円獔?zhí)行兩個(gè)命令弧烤,需要保證原子性忱屑,需要使用lua腳本來(lái)保證原子性。
簡(jiǎn)單來(lái)說(shuō)暇昂,就是在eval命令執(zhí)行Lua代碼的時(shí)候莺戒,Lua代碼將被當(dāng)成一個(gè)命令去執(zhí)行,并且直到eval命令執(zhí)行完成话浇,Redis才會(huì)執(zhí)行其他命令脏毯。
ps:如果不明白lua腳本的相關(guān)知識(shí)請(qǐng)谷歌學(xué)習(xí)。
總結(jié)
其實(shí)實(shí)現(xiàn)一個(gè)單機(jī)Redis版的分布式鎖并不是很難幔崖,只需要保證其四個(gè)特性即可食店。
如果文章中有錯(cuò)誤地方,還請(qǐng)大家指出赏寇,共同進(jìn)步吉嫩。