基于Redis實(shí)現(xiàn)分布式鎖

前言

最近在工作中使用到了分布式鎖鬓梅,所以在本文中記錄一下如何在Java中使用Redis實(shí)現(xiàn)分布式鎖

四個(gè)特性

在使用分布式鎖的時(shí)候,我們需要滿足以下四個(gè)條件:

  1. 互斥性 在同一時(shí)間只能有一個(gè)客戶端持有鎖拦耐。

  2. 容錯(cuò)性 客戶端可以實(shí)現(xiàn)加鎖和解鎖臣咖。

  3. 可靠性 即使在客戶端崩潰后蕊苗,無(wú)法主動(dòng)釋放鎖的情況下馁龟, 也可以保證后續(xù)客戶端持有該鎖想罕。

  4. 一致性 加鎖和解鎖需是同一客戶端,必須避免當(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)步吉嫩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗅定,隨后出現(xiàn)的幾起案子自娩,更是在濱河造成了極大的恐慌,老刑警劉巖渠退,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忙迁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碎乃,警方通過(guò)查閱死者的電腦和手機(jī)姊扔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梅誓,“玉大人恰梢,你說(shuō)我怎么就攤上這事佛南。” “怎么了嵌言?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵嗅回,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我摧茴,道長(zhǎng)绵载,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任苛白,我火速辦了婚禮尘分,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丸氛。我一直安慰自己,他們只是感情好著摔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布缓窜。 她就那樣靜靜地躺著,像睡著了一般谍咆。 火紅的嫁衣襯著肌膚如雪禾锤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天摹察,我揣著相機(jī)與錄音恩掷,去河邊找鬼。 笑死供嚎,一個(gè)胖子當(dāng)著我的面吹牛黄娘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播克滴,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逼争,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了劝赔?” 一聲冷哼從身側(cè)響起誓焦,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎着帽,沒想到半個(gè)月后杂伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仍翰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年赫粥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歉备。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡傅是,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喧笔,我是刑警寧澤帽驯,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站书闸,受9級(jí)特大地震影響尼变,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浆劲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一嫌术、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牌借,春花似錦度气、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至现柠,卻和暖如春院领,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背够吩。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工比然, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人周循。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓强法,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親湾笛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拟烫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容