溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

什么是Redis分布式鎖

發(fā)布時(shí)間:2021-06-23 14:29:44 來源:億速云 閱讀:146 作者:chen 欄目:編程語言

這篇文章主要介紹“什么是Redis分布式鎖”,在日常操作中,相信很多人在什么是Redis分布式鎖問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”什么是Redis分布式鎖”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

什么是分布式鎖

在分布式系統(tǒng)中,有些業(yè)務(wù)場景會(huì)用到分布式鎖,實(shí)現(xiàn)分布式鎖的方式有很多,本篇主要講根據(jù)Redis如何來實(shí)現(xiàn)。

首先我們要知道分布式鎖的一些基本特點(diǎn):

  1. 互斥性:只有一個(gè)客戶端可以持有鎖

  2. 不會(huì)產(chǎn)生死鎖:即使持有鎖的客戶端崩潰,也能保證后續(xù)其他客戶端可以獲得鎖

  3. 只有持有這把鎖的客戶端才能解鎖

下邊我們通過幾個(gè)例子來說明分布式鎖為什么需要以上3個(gè)特點(diǎn)。

不加客戶端校驗(yàn)解鎖
/**
 * 使用jedis客戶端實(shí)現(xiàn)分布式鎖
 * @Author: maomao
 * @Date: 2021-04-27 08:42
 */
public class DistLock {

    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";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param value 值
     * @param expireTime 超期時(shí)間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String value, String requestId, int expireTime) {
        // set支持多個(gè)參數(shù) NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 直接刪除解鎖,未判斷客戶端ID,會(huì)導(dǎo)致其他客戶端把鎖釋放
     * @param jedis
     * @param lockKey
     */
    public static void releaseLock1(Jedis jedis, String lockKey) {
        jedis.del(lockKey);
    }
}

上邊代碼是一個(gè)不驗(yàn)證客戶端的例子,加鎖是沒有問題的,但在解鎖時(shí)會(huì)有很大的問題。

什么是Redis分布式鎖

通過上圖可以看到,因?yàn)闆]有校驗(yàn)客戶端邏輯,Thread B可以直接解鎖,而Thread A程序還未執(zhí)行完,但已被解鎖,造成鎖失效。如果此時(shí)有其他客戶端加鎖是可以加鎖成功的。

那我們可以在代碼中增加一個(gè)客戶端校驗(yàn)不就可以了?

加客戶端校驗(yàn)解鎖
   /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請(qǐng)求標(biāo)識(shí)-修改此處為客戶端唯一標(biāo)致
     * @param expireTime 超期時(shí)間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        // set支持多個(gè)參數(shù) NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

   /**
     * 增加鎖判斷,但因判斷與刪除不是原子操作,在并發(fā)場景時(shí),會(huì)導(dǎo)致錯(cuò)誤刪除
     * @param jedis
     * @param lockKey
     * @param requestId
     */
    public static void releaseLock2(Jedis jedis, String lockKey, String requestId) {
        // 判斷加鎖與解鎖是不是同一個(gè)客戶端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此時(shí),這把鎖突然不是這個(gè)客戶端的,則會(huì)誤解鎖
            // 兩個(gè)操作不能保證原子性
            jedis.del(lockKey);
        }
    }

在解鎖代碼中可以看到,我們也增加了客戶端標(biāo)志校驗(yàn)應(yīng)該可以解決客戶端校驗(yàn)問題了吧?其實(shí)并沒有,我們要知道對(duì)redis來說,每個(gè)命令都是原子的,你的get與del方法是兩個(gè)命令,無法保證原子操作。也就是我們多線程中常見的i++;操作,其實(shí)他是由3個(gè)操作執(zhí)行。

那我們?nèi)绾未_保get與del的原子操作呢?我們可以使用lua腳本來實(shí)現(xiàn)。上述代碼我們可以調(diào)整為一個(gè)lua腳本。

   /**
     * 釋放分布式鎖,使用lua腳本刪除,可確保判斷與刪除的原子操作
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請(qǐng)求標(biāo)識(shí)
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(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;
    }

通過增加客戶端校驗(yàn)與解鎖的原子性就可以實(shí)現(xiàn)安全的解鎖。

什么是Redis分布式鎖

有了上邊的方式是不是就可以確保分布式鎖的全部問題了?并不是,還有一種場景沒有考慮到。

程序執(zhí)行時(shí)間超出鎖的過期時(shí)間

如果我們的加鎖程序執(zhí)行時(shí)間超出鎖過期時(shí)間時(shí),就會(huì)導(dǎo)致分布式鎖失效。此時(shí)其他客戶端是可以獲得到鎖的。如下圖:

什么是Redis分布式鎖

那么這種問題如何解決呢?

Redission

Redisson是一個(gè)在Redis的基礎(chǔ)上實(shí)現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid),提供了分布式和可擴(kuò)展的Java數(shù)據(jù)結(jié)構(gòu),比如分布式對(duì)象,分布式集合(Map、List、Queue、Set),分布式鎖等等功能,不需要自己去運(yùn)行一個(gè)服務(wù)實(shí)現(xiàn)。

什么是Redis分布式鎖

Redisson官網(wǎng)

Redisson Git地址

Redission是由一個(gè)中國人與俄羅斯人共同發(fā)起的,所以中文文檔比較詳細(xì)。

使用Redission實(shí)現(xiàn)分布式鎖

使用Redission可以很簡單的實(shí)現(xiàn)分布式鎖,代碼如下:

public static void main(String[] args) throws InterruptedException {
        //設(shè)定鎖標(biāo)志
        //會(huì)在redis中創(chuàng)建一個(gè)Hash,Key是客戶端UUID,value是鎖重入次數(shù)
        RLock rLock = redissonClient.getLock("lockKey");
        // 最多等待100秒、上鎖10s以后自動(dòng)解鎖
        if(rLock.tryLock(100,10, TimeUnit.SECONDS)){
            System.out.println("獲取鎖成功,此時(shí)可以查看redis中的數(shù)據(jù)!");
        }
        //線程等待后可在redis中查到
        Thread.sleep(20000);
        rLock.unlock();
}

什么是Redis分布式鎖

Redission不只可以實(shí)現(xiàn)獨(dú)占鎖,還可以實(shí)現(xiàn)如:可重入鎖、公平鎖、聯(lián)鎖、紅鎖、讀寫鎖等等。

什么是Redis分布式鎖

redission實(shí)現(xiàn)分布式鎖的邏輯基本與上邊我們講的原理差不多,它還解決了我們最后一個(gè)問題,程序執(zhí)行時(shí)間超出鎖過期時(shí)間的問題。

他使用了一個(gè)《看門狗》的概念來實(shí)現(xiàn)自動(dòng)續(xù)期。默認(rèn)最大續(xù)期時(shí)間30s,也就是說如果業(yè)務(wù)超出30秒還未執(zhí)行會(huì)自動(dòng)解鎖。

到此,關(guān)于“什么是Redis分布式鎖”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI