單機(jī)redis分布式鎖實(shí)現(xiàn)原理解析
最近我們有個(gè)服務(wù)經(jīng)常出現(xiàn)存儲(chǔ)的數(shù)據(jù)出現(xiàn)重復(fù),首先上一個(gè)系統(tǒng)流程圖:

用戶通過http請(qǐng)求可以通知任務(wù)中心結(jié)束掉自己發(fā)送的任務(wù),這時(shí)候任務(wù)中心會(huì)通過MQ通知結(jié)束服務(wù)去結(jié)束任務(wù)保存數(shù)據(jù),由于任務(wù)結(jié)束數(shù)據(jù)計(jì)算保存有一定延時(shí),所以存在用戶短時(shí)間內(nèi)多次結(jié)束同一個(gè)任務(wù),這時(shí)候就會(huì)導(dǎo)致我們結(jié)束服務(wù)對(duì)同一個(gè)任務(wù)保存多次數(shù)據(jù)。恰好我們也是用了redis,所以對(duì)于這個(gè)問題我當(dāng)時(shí)想到使用分布式鎖來解決,那么如何用redis實(shí)現(xiàn)分布式鎖呢?
首先要明確一個(gè)分布式鎖應(yīng)具備的原則:
互斥性。在任意時(shí)刻,只有一個(gè)客戶端能持有鎖;不會(huì)發(fā)生死鎖。即使一個(gè)客戶端持有鎖的期間崩潰而沒有主動(dòng)釋放鎖,也需要保證后續(xù)其他客戶端能夠加鎖成功;加鎖和解鎖必須是同一個(gè)客戶端;有高可用的獲取鎖和釋放鎖功能。
由于我們只使用了單機(jī)的redis,所以本文的實(shí)現(xiàn)不具備第四點(diǎn)原則。
我們這個(gè)鎖的實(shí)現(xiàn)就包括兩點(diǎn):加鎖、解鎖。首先看加鎖。先上代碼:
public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) throws Exception{
Jedis jedis = null;
try {
jedis = getJedisClient();
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
} finally {
returnResource(jedis);
}
}
我們的加鎖就是設(shè)置一個(gè)鍵值對(duì),并且滿足以下條件:
確保只有當(dāng)鍵不存在時(shí)才設(shè)置有效;設(shè)置的值必須是當(dāng)前客戶端生成的uuid;鍵必須要有過期時(shí)間。
這三點(diǎn)條件就可以滿足上述的原則1、原則2。
接下來看下解鎖,代碼如下:
public boolean releaseDistributedLock(String lockKey, String requestId) throws Exception{
Jedis jedis = null;
try {
jedis = getJedisClient();
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;
}finally {
returnResource(jedis);
}
}
解鎖是通過一段lua腳本實(shí)現(xiàn),邏輯如下:
1、獲取鎖鍵值看是否與當(dāng)初設(shè)置的值一致;
2、如果一致則刪除鍵。
由于解鎖過程分為兩步,為了確保原子性所以通過讓redis執(zhí)行l(wèi)ua腳本來實(shí)現(xiàn),校驗(yàn)鍵值可以確保加鎖解鎖都是同一個(gè)客戶端。
這樣一個(gè)簡(jiǎn)易的分布式鎖就實(shí)現(xiàn)完畢了,當(dāng)然在本文開頭就說了,這個(gè)實(shí)現(xiàn)只能滿足單機(jī)redis的情況,對(duì)于redis集群其實(shí)是不嚴(yán)謹(jǐn)?shù)?,?duì)于redis集群有一個(gè)redlock方案,我也在研究中,后面也會(huì)總結(jié)一下。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合WebSocket實(shí)現(xiàn)后端向前端發(fā)送消息的實(shí)例代碼
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合WebSocket實(shí)現(xiàn)后端向前端發(fā)送消息的相關(guān)資料,需要的朋友可以參考下2023-03-03
java的poi技術(shù)讀取和導(dǎo)入Excel實(shí)例
本篇文章主要介紹了java的poi技術(shù)讀取和導(dǎo)入Excel實(shí)例,報(bào)表輸出是Java應(yīng)用開發(fā)中經(jīng)常涉及的內(nèi)容,有需要的可以了解一下。2016-11-11
Java兩整數(shù)相除向上取整的方式詳解(Math.ceil())
在調(diào)外部接口獲取列表數(shù)據(jù)時(shí),需要判斷是否已經(jīng)取完了所有的值,因此需要用到向上取整,下面這篇文章主要給大家介紹了關(guān)于Java兩整數(shù)相除向上取整的相關(guān)資料,需要的朋友可以參考下2022-06-06
Java中的RASP機(jī)制實(shí)現(xiàn)詳解
這篇文章主要介紹了Java中的RASP實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
springboot2如何禁用自帶tomcat的session功能
這篇文章主要介紹了springboot2如何禁用自帶tomcat的session功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Spring Boot中Redis數(shù)據(jù)庫的使用實(shí)例
Spring Boot中除了對(duì)常用的關(guān)系型數(shù)據(jù)庫提供了優(yōu)秀的自動(dòng)化支持之外,對(duì)于很多NoSQL數(shù)據(jù)庫一樣提供了自動(dòng)化配置的支持。本篇文章主要介紹了Spring Boot中Redis的使用實(shí)例代碼,有興趣的開業(yè)了解一下。2017-04-04
簡(jiǎn)單了解SpringCloud運(yùn)行原理
這篇文章主要介紹了簡(jiǎn)單了解SpringCloud運(yùn)行原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

