日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

大家所推崇的Redis分布式锁真的就万无一失吗?

發布時間:2025/3/21 数据库 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大家所推崇的Redis分布式锁真的就万无一失吗? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在單實例JVM中,常見的處理并發問題的方法有很多,比如synchronized關鍵字進行訪問控制、volatile關鍵字、ReentrantLock等常用方法。但是在分布式環境中,上述方法卻不能在跨JVM場景中用于處理并發問題,當業務場景需要對分布式環境中的并發問題進行處理時,需要使用分布式鎖來實現。

分布式鎖,是指在分布式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。

目前比較常見的分布式鎖實現方案有以下幾種:

  • 基于數據庫,如MySQL

  • 基于緩存,如Redis

  • 基于Zookeeper、etcd等。

  • 在上一篇《基于數據庫實現分布式鎖》中介紹了如何基于數據庫實現分布式鎖,這里介紹一下如何使用緩存(Redis)實現分布式鎖。

    使用Redis實現分布式鎖最簡單的方案是使用命令SETNX。SETNX(SET if Not eXist)的使用方式為:SETNX key value,只在鍵key不存在的情況下,將鍵key的值設置為value,若鍵key存在,則SETNX不做任何動作。SETNX在設置成功時返回,設置失敗時返回0。當要獲取鎖時,直接使用SETNX獲取鎖,當要釋放鎖時,使用DEL命令刪除掉對應的鍵key即可。

    上面這種方案有一個致命問題,就是某個線程在獲取鎖之后由于某些異常因素(比如宕機)而不能正常的執行解鎖操作,那么這個鎖就永遠釋放不掉了。為此,我們可以為這個鎖加上一個超時時間。第一時間我們會聯想到Redis的EXPIRE命令(EXPIRE key seconds)。但是這里我們不能使用EXPIRE來實現分布式鎖,因為它與SETNX一起是兩個操作,在這兩個操作之間可能會發生異常,從而還是達不到預期的結果,示例如下:

    //?STEP?1 SETNX?key?value //?若在這里(STEP1和STEP2之間)程序突然崩潰,則無法設置過期時間,將有可能無法釋放鎖 //?STEP?2 EXPIRE?key?expireTime

    對此,正確的姿勢應該是使用“SET key value [EX seconds] [PX milliseconds] [NX|XX]”這個命令。

    從 Redis 2.6.12 版本開始, SET 命令的行為可以通過一系列參數來修改:

    • EX seconds : 將鍵的過期時間設置為 seconds 秒。 執行 SET key value EX seconds 的效果等同于執行 SETEX key seconds value 。

    • PX milliseconds : 將鍵的過期時間設置為 milliseconds 毫秒。 執行 SET key value PX milliseconds 的效果等同于執行 PSETEX key milliseconds value 。

    • NX : 只在鍵不存在時, 才對鍵進行設置操作。 執行 SET key value NX 的效果等同于執行 SETNX key value 。

    • XX : 只在鍵已經存在時, 才對鍵進行設置操作。

    舉例,我們需要創建一個分布式鎖,并且設置過期時間為10s,那么可以執行以下命令:

    SET?lockKey?lockValue?EX?10?NX 或者 SET?lockKey?lockValue?PX?10000?NX

    注意EX和PX不能同時使用,否則會報錯:ERR syntax error。

    解鎖的時候還是使用DEL命令來解鎖。

    修改之后的方案看上去很完美,但實際上還是會有問題。試想一下,某線程A獲取了鎖并且設置了過期時間為10s,然后在執行業務邏輯的時候耗費了15s,此時線程A獲取的鎖早已被Redis的過期機制自動釋放了。在線程A獲取鎖并經過10s之后,改鎖可能已經被其它線程獲取到了。當線程A執行完業務邏輯準備解鎖(DEL key)的時候,有可能刪除掉的是其它線程已經獲取到的鎖。

    所以最好的方式是在解鎖時判斷鎖是否是自己的。我們可以在設置key的時候將value設置為一個唯一值uniqueValue(可以是隨機值、UUID、或者機器號+線程號的組合、簽名等)。當解鎖時,也就是刪除key的時候先判斷一下key對應的value是否等于先前設置的值,如果相等才能刪除key,偽代碼示例如下:

    if?uniqueKey?==?GET(key)?{DEL?key }

    這里我們一眼就可以看出問題來:GET和DEL是兩個分開的操作,在GET執行之后且在DEL執行之前的間隙是可能會發生異常的。如果我們只要保證解鎖的代碼是原子性的就能解決問題了。這里我們引入了一種新的方式,就是Lua腳本,示例如下:

    if?redis.call("get",KEYS[1])?==?ARGV[1]?thenreturn?redis.call("del",KEYS[1]) elsereturn?0 end

    其中ARGV[1]表示設置key時指定的唯一值。

    由于Lua腳本的原子性,在Redis執行該腳本的過程中,其他客戶端的命令都需要等待該Lua腳本執行完才能執行。

    下面我們使用Jedis來演示一下獲取鎖和解鎖的實現,具體如下:

    public?boolean?lock(String?lockKey,?String?uniqueValue,?int?seconds){SetParams?params?=?new?SetParams();params.nx().ex(seconds);String?result?=?jedis.set(lockKey,?uniqueValue,?params);if?("OK".equals(result))?{return?true;}return?false; }public?boolean?unlock(String?lockKey,?String?uniqueValue){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(uniqueValue));if?(result.equals(1))?{return?true;}return?false; }

    如此就萬無一失了嗎?顯然不是!

    表面來看,這個方法似乎很管用,但是這里存在一個問題:在我們的系統架構里存在一個單點故障,如果Redis的master節點宕機了怎么辦呢?有人可能會說:加一個slave節點!在master宕機時用slave就行了!

    但是其實這個方案明顯是不可行的,因為Redis的復制是異步的。舉例來說:

  • 線程A在master節點拿到了鎖。

  • master節點在把A創建的key寫入slave之前宕機了。

  • slave變成了master節點。

  • 線程B也得到了和A還持有的相同的鎖。(因為原來的slave里面還沒有A持有鎖的信息)

  • 當然,在某些場景下這個方案沒有什么問題,比如業務模型允許同時持有鎖的情況,那么使用這種方案也未嘗不可。

    舉例說明,某個服務有2個服務實例:A和B,初始情況下A獲取了鎖然后對資源進行操作(可以假設這個操作很耗費資源),B沒有獲取到鎖而不執行任何操作,此時B可以看做是A的熱備。當A出現異常時,B可以“轉正”。當鎖出現異常時,比如Redis master宕機,那么B可能會同時持有鎖并且對資源進行操作,如果操作的結果是冪等的(或者其它情況),那么也可以使用這種方案。這里引入分布式鎖可以讓服務在正常情況下避免重復計算而造成資源的浪費。

    為了應對這種情況,antriez提出了Redlock算法。Redlock算法的主要思想是:假設我們有N個Redis master節點,這些節點都是完全獨立的,我們可以運用前面的方案來對前面單個的Redis master節點來獲取鎖和解鎖,如果我們總體上能在合理的范圍內或者N/2+1個鎖,那么我們就可以認為成功獲得了鎖,反之則沒有獲取鎖(可類比Quorum模型)。雖然Redlock的原理很好理解,但是其內部的實現細節很是復雜,要考慮很多因素,具體內容可以參考:https://redis.io/topics/distlock。有關Redlock的具體使用方式可以參考我之前轉載的兩篇文章《Redis分布式鎖最牛逼的實現》和《Redission實現Redis分布式鎖的N種姿勢》。

    Redlock算法也并非是“銀彈”,他除了條件有點苛刻外,其算法本身也被質疑。關于Redis分布式鎖的安全性問題,在分布式系統專家Martin Kleppmann和Redis的作者antirez之間就發生過一場爭論。這場爭論的內容大致如下:

    Martin Kleppmann發表了一篇blog,名字叫”How to do distributed locking “,地址為:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html。Martin在這篇文章中談及了分布式系統的很多基礎性的問題(特別是分布式計算的異步模型),對分布式系統的從業者來說非常值得一讀。

    Martin的那篇文章是在2016-02-08這一天發表的,但據Martin說,他在公開發表文章的一星期之前就把草稿發給了antirez進行review,而且他們之間通過email進行了討論。不知道Martin有沒有意料到,antirez對于此事的反應很快,就在Martin的文章發表出來的第二天,antirez就在他的博客上貼出了他對于此事的反駁文章,名字叫”Is Redlock safe?”,地址為http://antirez.com/news/101。

    這是高手之間的過招。antirez這篇文章也條例非常清晰,并且中間涉及到大量的細節。antirez認為,Martin的文章對于Redlock的批評可以概括為兩個方面(與Martin文章的前后兩部分對應):

    • 帶有自動過期功能的分布式鎖,必須提供某種fencing機制來保證對共享資源的真正的互斥保護。Redlock提供不了這樣一種機制。

    • Redlock構建在一個不夠安全的系統模型之上。它對于系統的記時假設(timing assumption)有比較強的要求,而這些要求在現實的系統中是無法保證的。

    antirez對這兩方面分別進行了反駁。

    首先,關于fencing機制。antirez對于Martin的這種論證方式提出了質疑:既然在鎖失效的情況下已經存在一種fencing機制能繼續保持資源的互斥訪問了,那為什么還要使用一個分布式鎖并且還要求它提供那么強的安全性保證呢?即使退一步講,Redlock雖然提供不了Martin所講的遞增的fencing token,但利用Redlock產生的隨機字符串(my_random_value)可以達到同樣的效果。這個隨機字符串雖然不是遞增的,但卻是唯一的,可以稱之為unique token。

    然后,antirez的反駁就集中在第二個方面上:關于算法在記時(timing)方面的模型假設。在我們前面分析Martin的文章時也提到過,Martin認為Redlock會失效的情況主要有三種:1. 時鐘發生跳躍;2. 長時間的GC pause;3. 長時間的網絡延遲。

    antirez肯定意識到了這三種情況對Redlock最致命的其實是第一點:時鐘發生跳躍。這種情況一旦發生,Redlock是沒法正常工作的。而對于后兩種情況來說,Redlock在當初設計的時候已經考慮到了,對它們引起的后果有一定的免疫力。所以,antirez接下來集中精力來說明通過恰當的運維,完全可以避免時鐘發生大的跳動,而Redlock對于時鐘的要求在現實系統中是完全可以滿足的。

    神仙打架,我們站旁邊看看就好。拋開這個層面而言,在理解Redlock算法時要理解“各個節點完全獨立”這個概念。Redis本身有幾種部署模式:單機模式、主從模式、哨兵模式、集群模式。比如采用集群模式部署,如果需要5個節點,那么就需要部署5個Redis Cluster集群。很顯然,這種要求每個master節點都獨立的Redlock算法條件有點苛刻,使用它所需要耗費的資源比較多,而且對每個節點都請求一次鎖所帶來的額外開銷也不可忽視。除非有實實在在的業務應用需求,或者有資源可以復用。

    使用Redis分布式鎖并不能做到萬無一失。一般而言,Redis分布式鎖的優勢在于性能,而如果要考慮到可靠性,那么Zookeeper、etcd這類的組件會比Redis要高。當然,在合適的環境下使用基于數據庫實現的分布式鎖會更合適,參考《基于數據庫實現分布式鎖》。

    不過就以可靠性而言,沒有任何組件是完全可靠的,程序員的價值不僅僅在于表象地如何靈活運用這些組件,而在于如何基于這些不可靠的組件構建一個可靠的系統。

    還是那句老話,選擇何種方案,合適最重要。

    參考資料:

    • https://redis.io/topics/distlock

    • https://www.jianshu.com/p/7e47a4503b87

    • http://ifeve.com/redis-lock/

    • https://www.cnblogs.com/linjiqin/p/8003838.html

    • http://zhangtielei.com/posts/blog-redlock-reasoning.html

    • http://zhangtielei.com/posts/blog-redlock-reasoning-part2.html

    總結

    以上是生活随笔為你收集整理的大家所推崇的Redis分布式锁真的就万无一失吗?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。