redis實現(xiàn)分布式鎖代碼實踐和場景問題解決方案

Redis為什么性能高?
1贬蛙、Redis基于內(nèi)存的
2长搀、Redis基于單線程逼侦,較少線程上下文切換
3匿辩、Redis的基于NIO的多路復(fù)用機制
4、Redis底層多種數(shù)據(jù)結(jié)構(gòu)榛丢,得益于數(shù)據(jù)存儲結(jié)構(gòu)

使用redis原子性命令解決分布式鎖問題刨析

1铲球、保證加鎖LockKey唯一性
2、保證加鎖KEY和expire設(shè)置過期時間是一條原子性命令
3晰赞、finally {}語句塊中釋放鎖稼病,保證釋放是當前線程的Redis分布式鎖。在加鎖之前生成一個clientId
在最后再判斷當前clientId是否一致掖鱼,然后再釋放Redis分布式鎖
4然走、極限情況下,請查看如下代碼

finally {
            // 在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖
            // (問題:判斷和刪除鎖不是原子性的戏挡,假設(shè)極限情況下key設(shè)置的過期時間是10秒)
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                // 剛好執(zhí)行完這個判斷時候9.9秒芍瑞,突然線程阻塞或者垃圾回收,鎖過期了褐墅,這個時候其他線程并發(fā)仍然能加鎖成功
                // 此時線程接著執(zhí)行刪除邏輯拆檬,釋放的是下一個線程的鎖,所有的問題都和鎖過期時間有關(guān)妥凳,所以需要鎖續(xù)命

                // 5竟贯、釋放鎖(問題:如果異常系統(tǒng)宕機無法釋放鎖,所以在加鎖前要設(shè)置過期時間)
                stringRedisTemplate.delete(lockKey);
            }

如果在刪除鎖之前逝钥,Redis的Key剛好過期澄耍,也可能導(dǎo)致釋放其他線程鎖。
這里就需要給鎖設(shè)置續(xù)命規(guī)則
引入Watch Dog看門狗程序:
通過定時任務(wù),每隔10秒檢查鎖是否失效齐莲,對鎖進行續(xù)命
解決方案:Redisson框架的底層實現(xiàn)機制(下面代碼有示例)

總結(jié):Redis分布式鎖所有場景問題都和Key的過期時間有關(guān)聯(lián)
   /**
     * 使用redis原子性命令解決分布式鎖
     */
    @PostMapping("/buyProduct1")
    public String buyProduct1() {

        // 1痢站、key代表商品唯一標識,保證永遠只會有一個請求成功獲取鎖
        final String lockKey = "lockKey_product1";
        final String clientId = UUID.randomUUID().toString();

        // 2选酗、redis的setNx原子命令解決分布式鎖
        // redis的setNx原子命令解決分布式鎖阵难,保證加鎖和過期時間設(shè)置原子性
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
        // 給當前鎖設(shè)置過期時間,避免死鎖(問題:加鎖和設(shè)置過期時間不是原子操作)
        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);

        if (Boolean.FALSE.equals(flag)) {
            return null;
        }

        // 3芒填、返回 true代表獲取分布式鎖成功 (問題:假設(shè)過期時間設(shè)置過段呜叫,導(dǎo)致業(yè)務(wù)邏輯未執(zhí)行完畢,stringRedisTemplate.delete刪除別的請求線程鎖)
        // 15s-->10s --> 5s / 8s-->5s --> 3s/ 6s-->5s --> 1s 假設(shè)多線程高并發(fā)請求殿衰,后續(xù)線程鎖會一直被釋放朱庆,導(dǎo)致鎖失效
        // 問題:自己加的鎖,被別的請求刪掉了(解決方案闷祥,在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖)
        try {
            // 從Redis獲取庫存
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
            if (stock <= 0) {
                System.out.println("扣減庫存失敗娱颊,庫存不足");
                return null;
            }

            // 4、扣減完畢直接更新庫存
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
            System.out.println("扣減庫存成功凯砍,剩余庫存realStock = " + realStock);

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            // 在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖
            // (問題:判斷和刪除鎖不是原子性的箱硕,假設(shè)極限情況下key設(shè)置的過期時間是10秒)
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                // 剛好執(zhí)行完這個判斷時候9.9秒,突然線程阻塞或者垃圾回收悟衩,鎖過期了剧罩,這個時候其他線程并發(fā)仍然能加鎖成功
                // 此時線程接著執(zhí)行刪除邏輯,釋放的是下一個線程的鎖座泳,所有的問題都和鎖過期時間有關(guān)惠昔,所以需要鎖續(xù)命

                // 5、釋放鎖(問題:如果異常系統(tǒng)宕機無法釋放鎖挑势,所以在加鎖前要設(shè)置過期時間)
                stringRedisTemplate.delete(lockKey);
            }
        }
        return null;
    }

Redisson實現(xiàn)分布式鎖解決鎖續(xù)命問題

 /**
     * 使用redis原子性命令解決分布式鎖
     */
    @PostMapping("/buyProduct1")
    public String buyProduct1() {

        // 1镇防、key代表商品唯一標識,保證永遠只會有一個請求成功獲取鎖
        final String lockKey = "lockKey_product1";

        // 2薛耻、獲取鎖對象
        RLock redissonLock = redisson.getLock(lockKey);
        redissonLock.lock();
        try {
            // 從Redis獲取庫存
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
            if (stock <= 0) {
                System.out.println("扣減庫存失敗营罢,庫存不足");
                return null;
            }
            // 扣減完畢直接更新庫存
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
            System.out.println("扣減庫存成功赏陵,剩余庫存realStock = " + realStock);

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            // 3饼齿、釋放鎖
            redissonLock.unlock();
        }
        return null;
    }

Redisson實現(xiàn)分布式鎖原理

Redis分布式鎖

1、加鎖機制
線程去獲取鎖蝙搔,獲取成功: 執(zhí)行 lua腳本缕溉,保存數(shù)據(jù)到 redis數(shù)據(jù)庫。
2吃型、watch dog自動延期機制
在一個分布式環(huán)境下证鸥,假如一個線程獲得鎖后,突然服務(wù)器宕機了,那么這個時候在一定時間后這個鎖會自動釋放枉层,你也可以設(shè)置鎖的有效時間(不設(shè)置默認30秒)泉褐,這樣的目的主要是防止死鎖的發(fā)生。

但在實際開發(fā)中會有下面一種情況:

 1 //設(shè)置鎖1秒過去
 2 redissonLock.lock("redisson", 1);
 3 /**
 4  * 業(yè)務(wù)邏輯需要咨詢2秒
 5  */
 6 redissonLock.release("redisson");
 7 
 8 /**
 9 * 線程1 進來獲得鎖后鸟蜡,線程一切正常并沒有宕機膜赃,但它的業(yè)務(wù)邏輯需要執(zhí)行2秒,這就會有個問題揉忘,在 線程1 執(zhí)行1秒后跳座,這個鎖就自動過期了,
10 * 那么這個時候 線程2 進來了泣矛。那么就存在 線程1和線程2 同時在這段業(yè)務(wù)邏輯里執(zhí)行代碼疲眷,這當然是不合理的。
11 * 而且如果是這種情況您朽,那么在解鎖時系統(tǒng)會拋異常狂丝,因為解鎖和加鎖已經(jīng)不是同一線程了
12 */

所以這個時候看門狗就出現(xiàn)了,它的作用就是 線程1 業(yè)務(wù)還沒有執(zhí)行完虚倒,時間就過了美侦,線程1 還想持有鎖的話,就會啟動一個 watch dog后臺線程魂奥,不斷的延長鎖 key的生存時間菠剩。
注意:正常這個看門狗線程是不啟動的,還有就是這個看門狗啟動后對整體性能也會有一定影響耻煤,所以不建議開啟看門狗具壮。

Redis分布式鎖提升性能的方案

1、通過控制代碼層加鎖的粒度哈蝇,提升性能
2棺妓、通過分段鎖提升性能。

Redis分段鎖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炮赦,一起剝皮案震驚了整個濱河市怜跑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吠勘,老刑警劉巖性芬,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剧防,居然都是意外死亡植锉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門峭拘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俊庇,“玉大人狮暑,你說我怎么就攤上這事』员ィ” “怎么了搬男?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彭沼。 經(jīng)常有香客問我止后,道長,這世上最難降的妖魔是什么溜腐? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任译株,我火速辦了婚禮,結(jié)果婚禮上挺益,老公的妹妹穿的比我還像新娘歉糜。我一直安慰自己,他們只是感情好望众,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布匪补。 她就那樣靜靜地躺著,像睡著了一般烂翰。 火紅的嫁衣襯著肌膚如雪夯缺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天甘耿,我揣著相機與錄音踊兜,去河邊找鬼。 笑死佳恬,一個胖子當著我的面吹牛捏境,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毁葱,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼垫言,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了倾剿?” 一聲冷哼從身側(cè)響起筷频,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎前痘,沒想到半個月后凛捏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡际度,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年葵袭,在試婚紗的時候發(fā)現(xiàn)自己被綠了涵妥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乖菱。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡坡锡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窒所,到底是詐尸還是另有隱情鹉勒,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布吵取,位于F島的核電站禽额,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏皮官。R本人自食惡果不足惜脯倒,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捺氢。 院中可真熱鬧藻丢,春花似錦、人聲如沸摄乒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馍佑。三九已至斋否,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拭荤,已是汗流浹背茵臭。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舅世,地道東北人笼恰。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像歇终,于是被迫代替她去往敵國和親社证。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

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