站長資訊網(wǎng)
        最全最豐富的資訊網(wǎng)站

        一起來聊聊如何使用Redis實現(xiàn)分布式鎖

        本篇文章給大家?guī)砹岁P(guān)于Redis中的相關(guān)知識,其中主要介紹了分布式鎖的相關(guān)問題,我們通常說的線程調(diào)用加鎖和釋放鎖的操作,實際上,一個線程調(diào)用加鎖操作,其實就是檢查鎖變量值是否為0,希望對大家有幫助。

        一起來聊聊如何使用Redis實現(xiàn)分布式鎖

        推薦學習:Redis學習教程

        單機上的鎖和分布式鎖的聯(lián)系與區(qū)別

        我們先來看下單機上的鎖。

        對于在單機上運行的多線程程序來說,鎖本身可以用一個變量表示。

        • 變量值為 0 時,表示沒有線程獲取鎖;
        • 變量值為 1 時,表示已經(jīng)有線程獲取到鎖了。

        我們通常說的線程調(diào)用加鎖和釋放鎖的操作,實際上,一個線程調(diào)用加鎖操作,其實就是檢查鎖變量值是否為 0。如果是 0,就把鎖的變量值設(shè)置為 1,表示獲取到鎖,如果不是 0,就返回錯誤信息,表示加鎖失敗,已經(jīng)有別的線程獲取到鎖了。而一個線程調(diào)用釋放鎖操作,其實就是將鎖變量的值置為 0,以便其它線程可以來獲取鎖。
        我用一段代碼來展示下加鎖和釋放鎖的操作,其中,lock 為鎖變量。

        acquire_lock(){   if lock == 0      lock = 1      return 1   else      return 0 }  release_lock(){   lock = 0   return 1 }

        和單機上的鎖類似,分布式鎖同樣可以用一個變量來實現(xiàn)。客戶端加鎖和釋放鎖的操作邏輯,也和單機上的加鎖和釋放鎖操作邏輯一致:加鎖時同樣需要判斷鎖變量的值,根據(jù)鎖變量值來判斷能否加鎖成功;釋放鎖時需要把鎖變量值設(shè)置為 0,表明客戶端不再持有鎖。
        但是,和線程在單機上操作鎖不同的是,在分布式場景下,鎖變量需要由一個共享存儲系統(tǒng)來維護,只有這樣,多個客戶端才可以通過訪問共享存儲系統(tǒng)來訪問鎖變量。相應(yīng)的,加鎖和釋放鎖的操作就變成了讀取、判斷和設(shè)置共享存儲系統(tǒng)中的鎖變量值。

        這樣一來,我們就可以得出實現(xiàn)分布式鎖的兩個要求。

        要求一:分布式鎖的加鎖和釋放鎖的過程,涉及多個操作。所以,在實現(xiàn)分布式鎖時,我們需要保證這些鎖操作的原子性;
        要求二:共享存儲系統(tǒng)保存了鎖變量,如果共享存儲系統(tǒng)發(fā)生故障或宕機,那么客戶端也就無法進行鎖操作了。在實現(xiàn)分布式鎖時,我們需要考慮保證共享存儲系統(tǒng)的可靠性,進而保證鎖的可靠性。

        好了,知道了具體的要求,接下來,我們就來學習下 Redis 是怎么實現(xiàn)分布式鎖的。

        其實,我們既可以基于單個 Redis 節(jié)點來實現(xiàn),也可以使用多個 Redis 節(jié)點實現(xiàn)。在這兩種情況下,鎖的可靠性是不一樣的。我們先來看基于單個 Redis 節(jié)點的實現(xiàn)方法。

        基于單個 Redis 節(jié)點實現(xiàn)分布式鎖
        作為分布式鎖實現(xiàn)過程中的共享存儲系統(tǒng),Redis 可以使用鍵值對來保存鎖變量,再接收和處理不同客戶端發(fā)送的加鎖和釋放鎖的操作請求。那么,鍵值對的鍵和值具體是怎么定的呢?
        我們要賦予鎖變量一個變量名,把這個變量名作為鍵值對的鍵,而鎖變量的值,則是鍵值對的值,這樣一來,Redis 就能保存鎖變量了,客戶端也就可以通過 Redis 的命令操作來實現(xiàn)鎖操作。
        為了幫助你理解,我畫了一張圖片,它展示 Redis 使用鍵值對保存鎖變量,以及兩個客戶端同時請求加鎖的操作過程。
        一起來聊聊如何使用Redis實現(xiàn)分布式鎖

        可以看到,Redis 可以使用一個鍵值對 lock_key:0 來保存鎖變量,其中,鍵是 lock_key,也是鎖變量的名稱,鎖變量的初始值是 0。

        我們再來分析下加鎖操作。

        在圖中,客戶端 A 和 C 同時請求加鎖。因為 Redis 使用單線程處理請求,所以,即使客戶端 A 和 C 同時把加鎖請求發(fā)給了 Redis,Redis 也會串行處理它們的請求。

        我們假設(shè) Redis 先處理客戶端 A 的請求,讀取 lock_key 的值,發(fā)現(xiàn) lock_key 為 0,所以,Redis 就把 lock_key 的 value 置為 1,表示已經(jīng)加鎖了。緊接著,Redis 處理客戶端 C 的請求,此時,Redis 會發(fā)現(xiàn) lock_key 的值已經(jīng)為 1 了,所以就返回加鎖失敗的信息。

        剛剛說的是加鎖的操作,那釋放鎖該怎么操作呢?其實,釋放鎖就是直接把鎖變量值設(shè)置為 0。

        我還是借助一張圖片來解釋一下。這張圖片展示了客戶端 A 請求釋放鎖的過程。當客戶端 A 持有鎖時,鎖變量 lock_key 的值為 1。客戶端 A 執(zhí)行釋放鎖操作后,Redis 將 lock_key 的值置為 0,表明已經(jīng)沒有客戶端持有鎖了。
        一起來聊聊如何使用Redis實現(xiàn)分布式鎖

        因為加鎖包含了三個操作(讀取鎖變量、判斷鎖變量值以及把鎖變量值設(shè)置為 1),而這三個操作在執(zhí)行時需要保證原子性。那怎么保證原子性呢?

        要想保證操作的原子性,有兩種通用的方法,分別是使用 Redis 的單命令操作和使用 Lua 腳本。那么,在分布式加鎖場景下,該怎么應(yīng)用這兩個方法呢?

        我們先來看下,Redis 可以用哪些單命令操作實現(xiàn)加鎖操作。

        首先是 SETNX 命令,它用于設(shè)置鍵值對的值。具體來說,就是這個命令在執(zhí)行時會判斷鍵值對是否存在,如果不存在,就設(shè)置鍵值對的值,如果存在,就不做任何設(shè)置。

        舉個例子,如果執(zhí)行下面的命令時,key 不存在,那么 key 會被創(chuàng)建,并且值會被設(shè)置為 value;如果 key 已經(jīng)存在,SETNX 不做任何賦值操作。

        SETNX key value

        對于釋放鎖操作來說,我們可以在執(zhí)行完業(yè)務(wù)邏輯后,使用 DEL 命令刪除鎖變量。不過,你不用擔心鎖變量被刪除后,其他客戶端無法請求加鎖了。因為 SETNX 命令在執(zhí)行時,如果要設(shè)置的鍵值對(也就是鎖變量)不存在,SETNX 命令會先創(chuàng)建鍵值對,然后設(shè)置它的值。所以,釋放鎖之后,再有客戶端請求加鎖時,SETNX 命令會創(chuàng)建保存鎖變量的鍵值對,并設(shè)置鎖變量的值,完成加鎖。
        總結(jié)來說,我們就可以用 SETNX 和 DEL 命令組合來實現(xiàn)加鎖和釋放鎖操作。下面的偽代碼示例顯示了鎖操作的過程,你可以看下。

        // 加鎖 SETNX lock_key 1 // 業(yè)務(wù)邏輯 DO THINGS // 釋放鎖 DEL lock_key

        不過,使用 SETNX 和 DEL 命令組合實現(xiàn)分布鎖,存在兩個潛在的風險。

        第一個風險是,假如某個客戶端在執(zhí)行了 SETNX 命令、加鎖之后,緊接著卻在操作共享數(shù)據(jù)時發(fā)生了異常,結(jié)果一直沒有執(zhí)行最后的 DEL 命令釋放鎖。因此,鎖就一直被這個客戶端持有,其它客戶端無法拿到鎖,也無法訪問共享數(shù)據(jù)和執(zhí)行后續(xù)操作,這會給業(yè)務(wù)應(yīng)用帶來影響。
        針對這個問題,一個有效的解決方法是,給鎖變量設(shè)置一個過期時間。這樣一來,即使持有鎖的客戶端發(fā)生了異常,無法主動地釋放鎖,Redis 也會根據(jù)鎖變量的過期時間,在鎖變量過期后,把它刪除。其它客戶端在鎖變量過期后,就可以重新請求加鎖,這就不會出現(xiàn)無法加鎖的問題了。

        我們再來看第二個風險。如果客戶端 A 執(zhí)行了 SETNX 命令加鎖后,假設(shè)客戶端 B 執(zhí)行了 DEL 命令釋放鎖,此時,客戶端 A 的鎖就被誤釋放了。如果客戶端 C 正好也在申請加鎖,就可以成功獲得鎖,進而開始操作共享數(shù)據(jù)。這樣一來,客戶端 A 和 C 同時在對共享數(shù)據(jù)進行操作,數(shù)據(jù)就會被修改錯誤,這也是業(yè)務(wù)層不能接受的。
        為了應(yīng)對這個問題,我們需要能區(qū)分來自不同客戶端的鎖操作,具體咋做呢?其實,我們可以在鎖變量的值上想想辦法。
        在使用 SETNX 命令進行加鎖的方法中,我們通過把鎖變量值設(shè)置為 1 或 0,表示是否加鎖成功。1 和 0 只有兩種狀態(tài),無法表示究竟是哪個客戶端進行的鎖操作。所以,我們在加鎖操作時,可以讓每個客戶端給鎖變量設(shè)置一個唯一值,這里的唯一值就可以用來標識當前操作的客戶端。在釋放鎖操作時,客戶端需要判斷,當前鎖變量的值是否和自己的唯一標識相等,只有在相等的情況下,才能釋放鎖。這樣一來,就不會出現(xiàn)誤釋放鎖的問題了。

        知道了解決方案,那么,在 Redis 中,具體是怎么實現(xiàn)的呢?我們再來了解下。
        在查看具體的代碼前,我要先帶你學習下 Redis 的 SET 命令。

        我們剛剛在說 SETNX 命令的時候提到,對于不存在的鍵值對,它會先創(chuàng)建再設(shè)置值(也就是“不存在即設(shè)置”),為了能達到和 SETNX 命令一樣的效果,Redis 給 SET 命令提供了類似的選項 NX,用來實現(xiàn)“不存在即設(shè)置”。如果使用了 NX 選項,SET 命令只有在鍵值對不存在時,才會進行設(shè)置,否則不做賦值操作。此外,SET 命令在執(zhí)行時還可以帶上 EX 或 PX 選項,用來設(shè)置鍵值對的過期時間。

        舉個例子,執(zhí)行下面的命令時,只有 key 不存在時,SET 才會創(chuàng)建 key,并對 key 進行賦值。另外,key 的存活時間由 seconds 或者 milliseconds 選項值來決定。

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

        有了 SET 命令的 NX 和 EX/PX 選項后,我們就可以用下面的命令來實現(xiàn)加鎖操作了。
        // 加鎖, unique_value作為客戶端唯一性的標識

        SET lock_key unique_value NX PX 10000

        其中,unique_value 是客戶端的唯一標識,可以用一個隨機生成的字符串來表示,PX 10000 則表示 lock_key 會在 10s 后過期,以免客戶端在這期間發(fā)生異常而無法釋放鎖。

        因為在加鎖操作中,每個客戶端都使用了一個唯一標識,所以在釋放鎖操作時,我們需要判斷鎖變量的值,是否等于執(zhí)行釋放鎖操作的客戶端的唯一標識,如下所示:
        //釋放鎖 比較unique_value是否相等,避免誤釋放

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

        這是使用 Lua 腳本(unlock.script)實現(xiàn)的釋放鎖操作的偽代碼,其中,KEYS[1]表示 lock_key,ARGV[1]是當前客戶端的唯一標識,這兩個值都是我們在執(zhí)行 Lua 腳本時作為參數(shù)傳入的。

        最后,我們執(zhí)行下面的命令,就可以完成鎖釋放操作了。

        redis-cli  --eval  unlock.script lock_key , unique_value

        你可能也注意到了,在釋放鎖操作中,我們使用了 Lua 腳本,這是因為,釋放鎖操作的邏輯也包含了讀取鎖變量、判斷值、刪除鎖變量的多個操作,而 Redis 在執(zhí)行 Lua 腳本時,可以以原子性的方式執(zhí)行,從而保證了鎖釋放操作的原子性。

        好了,到這里,你了解了如何使用 SET 命令和 Lua 腳本在 Redis 單節(jié)點上實現(xiàn)分布式鎖。但是,我們現(xiàn)在只用了一個 Redis 實例來保存鎖變量,如果這個 Redis 實例發(fā)生故障宕機了,那么鎖變量就沒有了。此時,客戶端也無法進行鎖操作了,這就會影響到業(yè)務(wù)的正常執(zhí)行。所以,我們在實現(xiàn)分布式鎖時,還需要保證鎖的可靠性。那怎么提高呢?這就要提到基于多個 Redis 節(jié)點實現(xiàn)分布式鎖的方式了。

        基于多個 Redis 節(jié)點實現(xiàn)高可靠的分布式鎖
        當我們要實現(xiàn)高可靠的分布式鎖時,就不能只依賴單個的命令操作了,我們需要按照一定的步驟和規(guī)則進行加解鎖操作,否則,就可能會出現(xiàn)鎖無法工作的情況。“一定的步驟和規(guī)則”是指啥呢?其實就是分布式鎖的算法。

        為了避免 Redis 實例故障而導(dǎo)致的鎖無法工作的問題,Redis 的開發(fā)者 Antirez 提出了分布式鎖算法 Redlock。

        Redlock 算法的基本思路,是讓客戶端和多個獨立的 Redis 實例依次請求加鎖,如果客戶端能夠和半數(shù)以上的實例成功地完成加鎖操作,那么我們就認為,客戶端成功地獲得分布式鎖了,否則加鎖失敗。這樣一來,即使有單個 Redis 實例發(fā)生故障,因為鎖變量在其它實例上也有保存,所以,客戶端仍然可以正常地進行鎖操作,鎖變量并不會丟失。

        我們來具體看下 Redlock 算法的執(zhí)行步驟。Redlock 算法的實現(xiàn)需要有 N 個獨立的 Redis 實例。接下來,我們可以分成 3 步來完成加鎖操作。

        第一步是,客戶端獲取當前時間。
        第二步是,客戶端按順序依次向 N 個 Redis 實例執(zhí)行加鎖操作。

        這里的加鎖操作和在單實例上執(zhí)行的加鎖操作一樣,使用 SET 命令,帶上 NX,EX/PX 選項,以及帶上客戶端的唯一標識。當然,如果某個 Redis 實例發(fā)生故障了,為了保證在這種情況下,Redlock 算法能夠繼續(xù)運行,我們需要給加鎖操作設(shè)置一個超時時間。

        如果客戶端在和一個 Redis 實例請求加鎖時,一直到超時都沒有成功,那么此時,客戶端會和下一個 Redis 實例繼續(xù)請求加鎖。加鎖操作的超時時間需要遠遠地小于鎖的有效時間,一般也就是設(shè)置為幾十毫秒。

        第三步是,一旦客戶端完成了和所有 Redis 實例的加鎖操作,客戶端就要計算整個加鎖過程的總耗時。

        客戶端只有在滿足下面的這兩個條件時,才能認為是加鎖成功。

        • 條件一:客戶端從超過半數(shù)(大于等于 N/2+1)的 Redis 實例上成功獲取到了鎖;
        • 條件二:客戶端獲取鎖的總耗時沒有超過鎖的有效時間。

        在滿足了這兩個條件后,我們需要重新計算這把鎖的有效時間,計算的結(jié)果是鎖的最初有效時間減去客戶端為獲取鎖的總耗時。如果鎖的有效時間已經(jīng)來不及完成共享數(shù)據(jù)的操作了,我們可以釋放鎖,以免出現(xiàn)還沒完成數(shù)據(jù)操作,鎖就過期了的情況。

        當然,如果客戶端在和所有實例執(zhí)行完加鎖操作后,沒能同時滿足這兩個條件,那么,客戶端向所有 Redis 節(jié)點發(fā)起釋放鎖的操作。

        在 Redlock 算法中,釋放鎖的操作和在單實例上釋放鎖的操作一樣,只要執(zhí)行釋放鎖的 Lua 腳本就可以了。這樣一來,只要 N 個 Redis 實例中的半數(shù)以上實例能正常工作,就能保證分布式鎖的正常工作了。

        所以,在實際的業(yè)務(wù)應(yīng)用中,如果你想要提升分布式鎖的可靠性,就可以通過 Redlock 算法來實現(xiàn)。

        推薦學習:Redis學習教程

        贊(0)
        分享到: 更多 (0)
        網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號
        主站蜘蛛池模板: 国产精品186在线观看在线播放| 久久精品国产免费一区| 国产精品美女久久久久久2018| 精品国产乱码久久久久久浪潮| 精品久久久久久无码专区| 久久精品成人免费国产片小草| 国产精品青草久久久久婷婷 | 久久精品人人做人人爽电影蜜月| 久久精品国产亚洲Aⅴ蜜臀色欲| 久久国产精品国产自线拍免费| 久久亚洲欧美国产精品| 鲸鱼传媒绿头鱼实验室之炮机测评日韩精品一级毛| 国产在视频线精品视频二代| 国自产偷精品不卡在线| 亚洲av永久无码精品秋霞电影影院| 久久丝袜精品中文字幕| 国产成人精品久久| 四虎永久在线精品884aa下载 | 国产精品国色综合久久| 日韩国产精品无码一区二区三区| 四虎成人精品| 精品偷自拍另类在线观看丰满白嫩大屁股ass | 1区1区3区4区产品芒果精品| 国产成人精品精品欧美| 国产精品亚洲片在线| 久久精品人人槡人妻人人玩AV| 亚洲精品国精品久久99热一| 合区精品久久久中文字幕一区| 久久精品成人免费国产片小草| 精品国产欧美一区二区三区成人| 99久久精品这里只有精品| 日韩精品成人一区二区三区| 精品国产一区二区三区免费| 国产精品视频网站你懂得| 97久久超碰成人精品网站| 国产精品人成在线播放新网站| 久久发布国产伦子伦精品| 日本aⅴ精品中文字幕| 国产日韩精品中文字无码| 国产精品免费大片| 久久福利青草精品资源站免费|