1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        七種方案!探討Redis分布式鎖的正確使用姿勢

        共 7610字,需瀏覽 16分鐘

         ·

        2021-04-30 23:23

        前言

        日常開發(fā)中,秒殺下單、搶紅包等等業(yè)務(wù)場景,都需要用到分布式鎖。而Redis非常適合作為分布式鎖使用。本文將分七個方案展開,跟大家探討Redis分布式鎖的正確使用方式。如果有不正確的地方,歡迎大家指出哈,一起學(xué)習(xí)一起進(jìn)步。

        公眾號:「撿田螺的小男孩」

        • 什么是分布式鎖
        • 方案一:SETNX + EXPIRE
        • 方案二:SETNX + value值是(系統(tǒng)時間+過期時間)
        • 方案三:使用Lua腳本(包含SETNX + EXPIRE兩條指令)
        • 方案四:SET的擴展命令(SET EX PX NX)
        • 方案五:SET EX PX NX  + 校驗唯一隨機值,再釋放鎖
        • 方案六: 開源框架~Redisson
        • 方案七:多機實現(xiàn)的分布式鎖Redlock

        什么是分布式鎖

        ?

        分布式鎖其實就是,控制分布式系統(tǒng)不同進(jìn)程共同訪問共享資源的一種鎖的實現(xiàn)。如果不同的系統(tǒng)或同一個系統(tǒng)的不同主機之間共享了某個臨界資源,往往需要互斥來防止彼此干擾,以保證一致性。

        ?

        我們先來看下,一把靠譜的分布式鎖應(yīng)該有哪些特征:

        • 「互斥性」: 任意時刻,只有一個客戶端能持有鎖。
        • 「鎖超時釋放」:持有鎖超時,可以釋放,防止不必要的資源浪費,也可以防止死鎖。
        • 「可重入性」:一個線程如果獲取了鎖之后,可以再次對其請求加鎖。
        • 「高性能和高可用」:加鎖和解鎖需要開銷盡可能低,同時也要保證高可用,避免分布式鎖失效。
        • 「安全性」:鎖只能被持有的客戶端刪除,不能被其他客戶端刪除

        Redis分布式鎖方案一:SETNX + EXPIRE

        提到Redis的分布式鎖,很多小伙伴馬上就會想到setnx+ expire命令。即先用setnx來搶鎖,如果搶到之后,再用expire給鎖設(shè)置一個過期時間,防止鎖忘記了釋放。

        ?

        SETNX 是SET IF NOT EXISTS的簡寫.日常命令格式是SETNX key value,如果 key不存在,則SETNX成功返回1,如果這個key已經(jīng)存在了,則返回0。

        ?

        假設(shè)某電商網(wǎng)站的某商品做秒殺活動,key可以設(shè)置為key_resource_id,value設(shè)置任意值,偽代碼如下:

        if(jedis.setnx(key_resource_id,lock_value) == 1){ //加鎖
            expire(key_resource_id,100); //設(shè)置過期時間
            try {
                do something  //業(yè)務(wù)請求
            }catch(){
          }
          finally {
               jedis.del(key_resource_id); //釋放鎖
            }
        }

        但是這個方案中,setnxexpire兩個命令分開了,「不是原子操作」。如果執(zhí)行完setnx加鎖,正要執(zhí)行expire設(shè)置過期時間時,進(jìn)程crash或者要重啟維護了,那么這個鎖就“長生不老”了,「別的線程永遠(yuǎn)獲取不到鎖啦」。

        Redis分布式鎖方案二:SETNX + value值是(系統(tǒng)時間+過期時間)

        為了解決方案一,「發(fā)生異常鎖得不到釋放的場景」,有小伙伴認(rèn)為,可以把過期時間放到setnx的value值里面。如果加鎖失敗,再拿出value值校驗一下即可。加鎖代碼如下:

        long expires = System.currentTimeMillis() + expireTime; //系統(tǒng)時間+設(shè)置的過期時間
        String expiresStr = String.valueOf(expires);

        // 如果當(dāng)前鎖不存在,返回加鎖成功
        if (jedis.setnx(key_resource_id, expiresStr) == 1) {
                return true;

        // 如果鎖已經(jīng)存在,獲取鎖的過期時間
        String currentValueStr = jedis.get(key_resource_id);

        // 如果獲取到的過期時間,小于系統(tǒng)當(dāng)前時間,表示已經(jīng)過期
        if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {

             // 鎖已過期,獲取上一個鎖的過期時間,并設(shè)置現(xiàn)在鎖的過期時間(不了解redis的getSet命令的小伙伴,可以去官網(wǎng)看下哈)
            String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
            
            if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                 // 考慮多線程并發(fā)的情況,只有一個線程的設(shè)置值和當(dāng)前值相同,它才可以加鎖
                 return true;
            }
        }
                
        //其他情況,均返回加鎖失敗
        return false;
        }

        這個方案的優(yōu)點是,巧妙移除expire單獨設(shè)置過期時間的操作,把「過期時間放到setnx的value值」里面來。解決了方案一發(fā)生異常,鎖得不到釋放的問題。但是這個方案還有別的缺點:

        ?
        • 過期時間是客戶端自己生成的(System.currentTimeMillis()是當(dāng)前系統(tǒng)的時間),必須要求分布式環(huán)境下,每個客戶端的時間必須同步。
        • 如果鎖過期的時候,并發(fā)多個客戶端同時請求過來,都執(zhí)行jedis.getSet(),最終只能有一個客戶端加鎖成功,但是該客戶端鎖的過期時間,可能被別的客戶端覆蓋
        • 該鎖沒有保存持有者的唯一標(biāo)識,可能被別的客戶端釋放/解鎖。
        ?

        Redis分布式鎖方案三:使用Lua腳本(包含SETNX + EXPIRE兩條指令)

        實際上,我們還可以使用Lua腳本來保證原子性(包含setnx和expire兩條指令),lua腳本如下:

        if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
           redis.call('expire',KEYS[1],ARGV[2])
        else
           return 0
        end;

        加鎖代碼如下:

         String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
                    " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";   
        Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values));
        //判斷是否成功
        return result.equals(1L);

        這個方案,跟方案二對比,你覺得哪個更好呢?

        Redis分布式鎖方案方案四:SET的擴展命令(SET EX PX NX)

        除了使用,使用Lua腳本,保證SETNX + EXPIRE兩條指令的原子性,我們還可以巧用Redis的SET指令擴展參數(shù)?。?code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">SET key value[EX seconds][PX milliseconds][NX|XX]),它也是原子性的!

        ?

        SET key value[EX seconds][PX milliseconds][NX|XX]

        • NX :表示key不存在的時候,才能set成功,也即保證只有第一個客戶端請求才能獲得鎖,而其他客戶端請求只能等其釋放鎖,才能獲取。
        • EX seconds :設(shè)定key的過期時間,時間單位是秒。
        • PX milliseconds: 設(shè)定key的過期時間,單位為毫秒
        • XX: 僅當(dāng)key存在時設(shè)置值
        ?

        偽代碼demo如下:

        if(jedis.set(key_resource_id, lock_value, "NX""EX", 100s) == 1){ //加鎖
            try {
                do something  //業(yè)務(wù)處理
            }catch(){
          }
          finally {
               jedis.del(key_resource_id); //釋放鎖
            }
        }

        但是呢,這個方案還是可能存在問題:

        • 問題一:「鎖過期釋放了,業(yè)務(wù)還沒執(zhí)行完」。假設(shè)線程a獲取鎖成功,一直在執(zhí)行臨界區(qū)的代碼。但是100s過去后,它還沒執(zhí)行完。但是,這時候鎖已經(jīng)過期了,此時線程b又請求過來。顯然線程b就可以獲得鎖成功,也開始執(zhí)行臨界區(qū)的代碼。那么問題就來了,臨界區(qū)的業(yè)務(wù)代碼都不是嚴(yán)格串行執(zhí)行的啦。
        • 問題二:「鎖被別的線程誤刪」。假設(shè)線程a執(zhí)行完后,去釋放鎖。但是它不知道當(dāng)前的鎖可能是線程b持有的(線程a去釋放鎖時,有可能過期時間已經(jīng)到了,此時線程b進(jìn)來占有了鎖)。那線程a就把線程b的鎖釋放掉了,但是線程b臨界區(qū)業(yè)務(wù)代碼可能都還沒執(zhí)行完呢。

        方案五:SET EX PX NX  + 校驗唯一隨機值,再刪除

        既然鎖可能被別的線程誤刪,那我們給value值設(shè)置一個標(biāo)記當(dāng)前線程唯一的隨機數(shù),在刪除的時候,校驗一下,不就OK了嘛。偽代碼如下:

        if(jedis.set(key_resource_id, uni_request_id, "NX""EX", 100s) == 1){ //加鎖
            try {
                do something  //業(yè)務(wù)處理
            }catch(){
          }
          finally {
               //判斷是不是當(dāng)前線程加的鎖,是才釋放
               if (uni_request_id.equals(jedis.get(key_resource_id))) {
                jedis.del(lockKey); //釋放鎖
                }
            }
        }

        在這里,「判斷是不是當(dāng)前線程加的鎖」「釋放鎖」不是一個原子操作。如果調(diào)用jedis.del()釋放鎖的時候,可能這把鎖已經(jīng)不屬于當(dāng)前客戶端,會解除他人加的鎖。

        為了更嚴(yán)謹(jǐn),一般也是用lua腳本代替。lua腳本如下:

        if redis.call('get',KEYS[1]) == ARGV[1] then 
           return redis.call('del',KEYS[1]) 
        else
           return 0
        end;

        Redis分布式鎖方案六:Redisson框架

        方案五還是可能存在「鎖過期釋放,業(yè)務(wù)沒執(zhí)行完」的問題。有些小伙伴認(rèn)為,稍微把鎖過期時間設(shè)置長一些就可以啦。其實我們設(shè)想一下,是否可以給獲得鎖的線程,開啟一個定時守護線程,每隔一段時間檢查鎖是否還存在,存在則對鎖的過期時間延長,防止鎖過期提前釋放。

        當(dāng)前開源框架Redisson解決了這個問題。我們一起來看下Redisson底層原理圖吧:

        只要線程一加鎖成功,就會啟動一個watch dog看門狗,它是一個后臺線程,會每隔10秒檢查一下,如果線程1還持有鎖,那么就會不斷的延長鎖key的生存時間。因此,Redisson就是使用watch dog解決了「鎖過期釋放,業(yè)務(wù)沒執(zhí)行完」問題。

        Redis分布式鎖方案七:多機實現(xiàn)的分布式鎖Redlock+Redisson

        前面六種方案都只是基于單機版的討論,還不是很完美。其實Redis一般都是集群部署的:

        如果線程一在Redis的master節(jié)點上拿到了鎖,但是加鎖的key還沒同步到slave節(jié)點。恰好這時,master節(jié)點發(fā)生故障,一個slave節(jié)點就會升級為master節(jié)點。線程二就可以獲取同個key的鎖啦,但線程一也已經(jīng)拿到鎖了,鎖的安全性就沒了。

        為了解決這個問題,Redis作者 antirez提出一種高級的分布式鎖算法:Redlock。Redlock核心思想是這樣的:

        ?

        搞多個Redis master部署,以保證它們不會同時宕掉。并且這些master節(jié)點是完全相互獨立的,相互之間不存在數(shù)據(jù)同步。同時,需要確保在這多個master實例上,是與在Redis單實例,使用相同方法來獲取和釋放鎖。

        ?

        我們假設(shè)當(dāng)前有5個Redis master節(jié)點,在5臺服務(wù)器上面運行這些Redis實例。

        RedLock的實現(xiàn)步驟:如下

        ?
        • 1.獲取當(dāng)前時間,以毫秒為單位。
        • 2.按順序向5個master節(jié)點請求加鎖??蛻舳嗽O(shè)置網(wǎng)絡(luò)連接和響應(yīng)超時時間,并且超時時間要小于鎖的失效時間。(假設(shè)鎖自動失效時間為10秒,則超時時間一般在5-50毫秒之間,我們就假設(shè)超時時間是50ms吧)。如果超時,跳過該master節(jié)點,盡快去嘗試下一個master節(jié)點。
        • 3.客戶端使用當(dāng)前時間減去開始獲取鎖時間(即步驟1記錄的時間),得到獲取鎖使用的時間。當(dāng)且僅當(dāng)超過一半(N/2+1,這里是5/2+1=3個節(jié)點)的Redis master節(jié)點都獲得鎖,并且使用的時間小于鎖失效時間時,鎖才算獲取成功。(如上圖,10s> 30ms+40ms+50ms+4m0s+50ms)
        • 如果取到了鎖,key的真正有效時間就變啦,需要減去獲取鎖所使用的時間。
        • 如果獲取鎖失?。]有在至少N/2+1個master實例取到鎖,有或者獲取鎖時間已經(jīng)超過了有效時間),客戶端要在所有的master節(jié)點上解鎖(即便有些master節(jié)點根本就沒有加鎖成功,也需要解鎖,以防止有些漏網(wǎng)之魚)。
        ?

        簡化下步驟就是:

        • 按順序向5個master節(jié)點請求加鎖
        • 根據(jù)設(shè)置的超時時間來判斷,是不是要跳過該master節(jié)點。
        • 如果大于等于3個節(jié)點加鎖成功,并且使用的時間小于鎖的有效期,即可認(rèn)定加鎖成功啦。
        • 如果獲取鎖失敗,解鎖!

        Redisson實現(xiàn)了redLock版本的鎖,有興趣的小伙伴,可以去了解一下哈~

        參考與感謝

        Reference

        [1]

        redis系列:分布式鎖: https://juejin.cn/post/6844903656911798285

        [2]

        淺析 Redis 分布式鎖解決方案: https://www.infoq.cn/article/dvaaj71f4fbqsxmgvdce

        [3]

        細(xì)說Redis分布式鎖??: https://juejin.cn/post/6844904082860146695#heading-3


        瀏覽 39
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲Aⅴ| 亚洲91精品| 一区二区三区网| 黄色免费在线观看| 亚洲精品久久久久久久久蜜桃 | 麻豆成人精品国产免费| 91亚洲免费视频| 91一起草高清资源| 九九re精品视频在线观看| 91丨九色丨蝌蚪丨对白| 午夜成人大片| 日本黄色视频电影| 亚洲在线免费观看| 大香蕉国产在线| 中文字字幕在线中文乱码| 99久久婷婷国产综合精品电影 | 91在线免费视频观看| 超小超嫩国产合集六部| 欧美黑人大吊| 操B在线视频| 国产欧美日韩视频| 美日韩AV| 免费人成视频在线播放| 久久免费视频网站| 蜜臀久久99久久久久久宅男 | 人人操天天操| 人妻FrXXeeXXee护士| 北条麻妃无码中文| 青草无码视频| 91亚洲免费| 国产免费看片| 成人色色网站| 五月天综合| 欧美A片在线| 亚洲中文久久| 亚洲激情内射| 丁香五月天av| 肏屄一区| 日韩免费中文字幕A片| 中文字幕第11页| 真人BBwBBWBBw另类视频| 大香蕉尹人在看| 伊人黄色视频| 成人黄色录像| 老司机午夜视频| 草草影院CCYYCOM屁屁影院合集限制影院| 在线免费观看成人网站| 丰满人妻一区二区三区46| 黄色爱爱视频| 揉BBB搡BBB搡BBB| 97精品超碰一区二区三区| 日韩欧美视频一区国产欧美在线 | 七区九区一区在线| 国产女人18| 成人免费无码激情AV片| AV中文字幕电影| 国产区视频| 国产乱子伦一区二区三区视频| 大香蕉一本| 97爱视频| 中文字幕免费毛片| 色播视频在线观看| 国精产品一区一区三区有限公司杨| 黑人在线视频| 国产在线看| 极品少妇久久久| 日本成人电影一区二区三区| 特级西西人体444www高清大胆| 一级黄色电影免费| 91成人在线电影| 亚洲AV成人无码AV小说| 免费av片| 精品孕妇孕交无码专区| 少妇搡BBBB搡BBB搡造水多| 成人亚洲A片V一区二区三区蜜月| WWWA片| 在线黄色小视频| 国产草逼视频| 黄色动态视频| 欧美黄色电影网站| 欧美性爱在线观看| 欧美成人在线观看| 一级a片激情啪啪免费观| 一品国精和二品国精的文化意义| 国产真人一级a爱做片| www.伊人大香蕉| 欧美性爱在线网站| 国产永久在线| 91妻人人澡人人爽人人精品| 婷婷午夜福利| 色色综合视频| 美女大香蕉| 国产棈品久久久久久久久久九秃| 免费一级无码成人片| 久久悠悠| 怡红院av| 人妻人人操| 久久无码区| 婷婷开心五月天| 五月丁香婷婷久久| jzzijzzij亚洲成熟少妇在线观看| 国产无码电影在线观看| 亚洲在线成人| 青青草在线免费视频| 亚洲欧美成人在线观看| 国产玖玖| 香蕉久久网| 国产精品操逼视频| 天天色色| 亚洲日本三级片| 毛片2| 欧美性受XXXX黑人XYX性爽一 | 国产精品s色| 欧美成人免费网站| 成人精品影视| 亚洲中文无码在线| 国产美女精品久久AV爽| 国产精品久久久91| 天天色天天色| 黄色电影视频在线| 国产精品婷婷久久久| 永久免费一区二区| 欧美成人在线观看视频| 国产三级片网址| 欧美激情网站| 欧美一区三区视频z| 国产伦精品一区二区三区色大师| 精品九九九九九| 综合亚洲视频| 日韩三级片无码| 夜夜高潮夜夜爽| 日韩操b| 影音先锋成人资源AV在线观看| 久久91人妻无码精品蜜桃HD| 日日夜夜超碰| 人人操人人操人人操人人操 | 黄色激情视频网站| 亚洲一级av| 青草影视久久| A片免费的| 色综合天| www.18av| 超碰A片| 99无码视频| 欧美777| 大香蕉伊人成人| 伊人色综合网| 高清无码不卡在线观看| 色激情五月天| 人人操免费| 国产一级内射| 人人干人人操人人爱| 色婷婷在线视频播放| 亚洲免费视频一区| 人人操人人摸人人看| 国产理论电影在线观看| 嫩BBB槡BBBB槡BBB| 北条麻妃无码精品AV怎么看| 夜夜撸夜夜操| 豆花视频一区二区| 免费在线观看黄色片| 超碰少妇| 天天色视频| 女同一区二区三区| 亚洲婷婷AV| 丁香五月情| 99超碰在线观看| 国产大鸡吧| 免费在线性爱视频| 中国a一片一级一片| 欧一美一婬一伦一区二区三区自慰,| 三级片无码在线观看| 国产无码毛片| 亚洲中文无码第一页| 免费的a片| 欧美人操逼视频| 无码一区二| 天天干天天操天天射| 91人妻一区二区| 一级a片在线播放| 蜜桃操逼| 欧美性69| 热逼视频| 久久婷婷精品| 黄色成人免费视频| 婷婷社区五月天| 无码免费高清| 成人国产精品在线观看| 免费爱爱视频网站| 国产九九在线视频| 国产精视频| 黄色大片网站| 韩日成人| 国产午夜精品一区二区三区牛牛 | 五月天在线观看| 天天视频黄| 日本无码区| 91婷婷射| 一本道视频在线| 亚洲日韩国产AV无码无码精品| 极品美女扒开粉嫩小泬高潮一| 无码视频一二三区| 91成人视频在线免费观看| 大香伊人中文字幕精品| 人妻日韩精品中文字幕| 加勒比一区二区| 自拍视频一区| 操久久久| 亚洲无码免费视频| 国产精品2| 色综合加勒比| 国产毛片在线看| 人人操碰成人网| 久久久久女人精品毛片九一| 翔田千里50岁无码| 一级A片免费观看| 国产三级日本三级国产三级| 国产精品乱伦片| 婷婷久久网| 中文字字幕在线中文乱码电影| 国产精品久久久91| 国产女人高潮毛片| 免费操逼网| 亚洲成人影片| 二级黄色视频| 露脸老熟女91集合| 黄色小视频免费看| 亚洲中文自拍| 黄色视频A| 欧美一級黃色A片免費看| 秋霞福利| 亚洲精品无码中文| www.俺去| 国产黄色A片| 99er这里只有精品| www久久99| 操逼操逼操逼操逼| 亚洲中文无码第一页| 日日舔| 91探花视频在线观看| 国产精品激情| 特级西西444www大胆高清图片| 成年人黄色视频在线观看| 手机看片久久| 亚洲精品另类| 久操免费观看| 日韩五码| www日韩无码| 一区二区三区无码区| 婷婷久久久久| 吴梦梦md0069| 黑人亚洲娇小videos∞| 亚洲一区在线免费观看| 亚洲五月婷婷| 一区二区免费在线观看| 黄色小视频在线免费看| 一区二区不卡| 少妇搡BBBB搡BBB搡18禁| 亚洲日韩免费观看| 国产黄色免费看| 色v在线| 蜜臀久久99精品久久久兰草影视| 中文人妻第9页| 精品人人人人| 国产欧美日韩一区二区三区| 欧美一区二区在线| www.射| 国产精品VA| 免费播放片色情A片| 日韩一区二区三区四区久久久精品有吗 | 婷婷五月精品中文字幕| 91大神在线资源观看无广告| 综合精品7799| 亚洲AV无码一区| www、久久| 亚洲精品久久久久毛片A级绿茶 | 国产精品自拍三级| 国产精品福利视频| 日韩不卡免费| 黄视频免费| 日韩欧美色图| 久射精品| 亚洲无码在线播放视频| 亚洲性网| 久久久久久无码精品亚洲日韩麻豆| 韩国三级HD久久精品HD| www.日韩精品| 欧美日韩色视频| 宗合久久| 一区二区免费视频| 国产真实乱婬A片久久久老牛| 欧美成人午夜| 四虎影院中文字幕| 亚洲AV无码成人精品区在线欢看| 亚洲第一页在线| 久久综合成人| 密臀久久| 天天射天天| 男人天堂资源网| 一道本无码在线|