Redis分布式鎖

設(shè)計(jì)思路
基于 Redis 的 Setnx 命令:在指定的 key 不存在時(shí),為 key 設(shè)置指定的值命贴。

具體思路和實(shí)現(xiàn)步驟梢莽,詳見代碼。

import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Redis分布式鎖
 *
 * @author Alisallon
 * Created on 2021/4/25 9:34.
 */
@Component
public class RedisLock {
    /**
     * 保存鎖以及過期時(shí)間,用于解決釋放鎖造成的問題
     */
    private static final Map<String, Long> LOCK_MAP = new HashMap<>();
    private final StringRedisTemplate redisTemplate;

    public RedisLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 嘗試獲取分布式鎖(加鎖)
     *
     * @param lock   鎖名稱
     * @param expire 鎖過期時(shí)間
     * @return 是否獲取到
     */
    public boolean lock(String lock, long expire) {
        try {
            AtomicLong expireAt = new AtomicLong();
            Object result = redisTemplate.execute((RedisCallback<Object>) connection -> {
                // 嘗試給鎖設(shè)置值(保存的是未來的過期時(shí)間)
                expireAt.set(System.currentTimeMillis() + expire + 1);
                Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt.get()).getBytes());
                if (Optional.ofNullable(acquire).orElse(false)) {
                    // 設(shè)置值成功,即獲取鎖成功(加鎖成功)
                    return true;
                }
                // 設(shè)置值失敗,即沒有獲取到鎖,獲取鎖的對(duì)應(yīng)的值(過期時(shí)間)
                byte[] value = connection.get(lock.getBytes());
                if (Objects.nonNull(value) && value.length > 0) {
                    // 獲取鎖的對(duì)應(yīng)的值(過期時(shí)間)成功
                    long expireTime = Long.parseLong(new String(value));
                    // 判斷鎖是否過期
                    if (expireTime < System.currentTimeMillis()) {
                        // 鎖已經(jīng)過期,表示沒有其他程序在占用鎖(不能排除占用鎖的程序,因?yàn)檫壿嫃?fù)雜造成執(zhí)行時(shí)間太長或者程序掛掉了,還沒來得及釋放鎖)
                        // 這里為了防止死鎖,直接對(duì)已過期的鎖重新設(shè)置過期時(shí)間,同時(shí)獲得設(shè)置新值之前的舊過期時(shí)間
                        expireAt.set(System.currentTimeMillis() + expire + 1);
                        byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(expireAt.get()).getBytes());
                        if (Optional.ofNullable(oldValue).isPresent()) {
                            // 重新判斷設(shè)置新值之前的舊過期時(shí)間是否真的過期,因?yàn)榭赡軙?huì)同時(shí)存在多個(gè)程序在競爭該鎖
                            // 如果oldValue還未過期,說明該鎖被其他程序搶走了
                            // 如果oldValue已過期,說明該鎖未被占用,當(dāng)前程序可以獲得該鎖
                            return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                        }
                    }
                }
                // 鎖的對(duì)應(yīng)的值失敗,返回獲取鎖失敗
                return false;
            });
            if (Optional.ofNullable(result).map(t -> (Boolean) result).orElse(false)) {
                // 獲取鎖成功
                // 在當(dāng)前程序中保存該鎖和過期時(shí)間,會(huì)在釋放鎖時(shí)使用
                LOCK_MAP.put(lock, expireAt.longValue());
                return true;
            }
            // 獲取鎖失敗
            return false;
        } catch (Exception e) {
            // 獲取鎖異常,返回獲取鎖失敗
            return false;
        }
    }

    /**
     * 釋放鎖
     * 必須和上面的lock方法成對(duì)出現(xiàn)
     * 需要注意,如果lock方法后面的執(zhí)行邏輯里有try-catch,一定要在finally中釋放鎖
     *
     * @param lock 鎖名稱
     */
    public void release(String lock) {
        // 當(dāng)當(dāng)前占用鎖的程序因?yàn)檫壿嫃?fù)雜造成執(zhí)行時(shí)間太長(執(zhí)行正常無誤),超過了鎖的超時(shí)時(shí)間,這時(shí)鎖可能會(huì)被其他程序搶走
        // 如果直接delete,可能會(huì)把其他程序搶走的鎖釋放,并且被另一個(gè)程序搶走,這會(huì)造成多個(gè)程序同一種業(yè)務(wù)邏輯并發(fā)執(zhí)行,可能會(huì)造成數(shù)據(jù)不一致的問題
        // 為了解決這個(gè)問題,引入了LOCK_MAP
        // 如果LOCK_MAP中存在該鎖,需要判斷該鎖的超時(shí)時(shí)間
        if (LOCK_MAP.containsKey(lock)) {
            // 已存在該鎖
            long expireAt = LOCK_MAP.get(lock);
            if (expireAt <= System.currentTimeMillis()) {
                // 該鎖已過期,此時(shí)無需手動(dòng)釋放鎖,因?yàn)樵撴i可能已經(jīng)被其他程序搶走了
                // 如果釋放了鎖,可能釋放的不是本程序獲得的鎖,而是別的程序已搶走的鎖,就可能會(huì)出現(xiàn)上面說的數(shù)據(jù)不一致的問題
                return;
            }
            // 該鎖還未過期,可以釋放鎖,因?yàn)槟苤鲃?dòng)調(diào)用release方法的一定是已獲得鎖的程序
        }
        try {
            // 釋放鎖
            redisTemplate.delete(lock);
            // 當(dāng)前程序移除鎖
            LOCK_MAP.remove(lock);
        } catch (Exception e) {
            // 釋放鎖異常,可以無視
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臼朗,一起剝皮案震驚了整個(gè)濱河市邻寿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌视哑,老刑警劉巖绣否,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挡毅,居然都是意外死亡蒜撮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門跪呈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來段磨,“玉大人,你說我怎么就攤上這事耗绿∑恢В” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵误阻,是天一觀的道長债蜜。 經(jīng)常有香客問我,道長究反,這世上最難降的妖魔是什么寻定? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮奴紧,結(jié)果婚禮上特姐,老公的妹妹穿的比我還像新娘。我一直安慰自己黍氮,他們只是感情好唐含,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布浅浮。 她就那樣靜靜地躺著,像睡著了一般捷枯。 火紅的嫁衣襯著肌膚如雪滚秩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天淮捆,我揣著相機(jī)與錄音郁油,去河邊找鬼。 笑死攀痊,一個(gè)胖子當(dāng)著我的面吹牛桐腌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苟径,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼案站,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了棘街?” 一聲冷哼從身側(cè)響起蟆盐,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遭殉,沒想到半個(gè)月后石挂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡险污,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年痹愚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罗心。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡里伯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渤闷,到底是詐尸還是另有隱情,我是刑警寧澤脖镀,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布飒箭,位于F島的核電站,受9級(jí)特大地震影響蜒灰,放射性物質(zhì)發(fā)生泄漏弦蹂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一强窖、第九天 我趴在偏房一處隱蔽的房頂上張望凸椿。 院中可真熱鬧,春花似錦翅溺、人聲如沸脑漫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽优幸。三九已至吨拍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間网杆,已是汗流浹背羹饰。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碳却,地道東北人队秩。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像昼浦,于是被迫代替她去往敵國和親馍资。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • 大家好座柱,我是walking迷帜,原文首發(fā)于公眾號(hào)編程大道。感謝你打開這篇文章色洞,請(qǐng)認(rèn)真閱讀下去吧戏锹。今天我們聊聊redis...
    BiggerBoy閱讀 659評(píng)論 0 8
  • 一 前言 我為什么要寫分布式鎖呢,最近工作中寫一個(gè)查詢接口火诸,因?yàn)檫壿嫃?fù)雜锦针,不希望用戶不停的點(diǎn)擊,需要過濾掉重復(fù)的請(qǐng)...
    漫慢行閱讀 304評(píng)論 0 0
  • 基于Spring Boot AOP 實(shí)現(xiàn)分布式鎖 AOP AOP 的全稱為 Aspect Oriented Pro...
    卡斯特梅的雨傘閱讀 324評(píng)論 0 1
  • 為什么需要分布式鎖置蜀? 在傳統(tǒng)單體應(yīng)用單機(jī)部署的情況下奈搜,可以使用Java并發(fā)相關(guān)的鎖,如ReentrantLcok或...
    空語閱讀 331評(píng)論 0 0
  • 基于單Redis節(jié)點(diǎn)的分布式鎖 組件依賴 首先我們要通過Maven引入Jedis開源組件盯荤,在pom.xml文件加入...
    GeekerLou閱讀 813評(píng)論 0 7