RedLock到底是不是Redis分佈式鎖分佈式環境下的銀彈?

  • 我曾跨入山巔,也曾步入低谷,兩者都使我受益良多!
  • I've been to the top, and I've fallen to the bottom, and I've learned a lot from both!

1、概述

在這個技術不斷更新迭代的狀況下,分佈式這個概念,在企業中的權重愈來愈高!談及分佈式時,不可避免必定會提到分佈式鎖,現階段分佈式鎖的實現方式主流的有三種實現方式, ZookeeperDBRedis,咱們本篇文章以Redis爲例!java

從咱們的角度來看,這三個屬性是有效使用分佈式鎖所需的最低保證。web

  1. 安全特性:互斥。在任何給定時刻,只有一個客戶端能夠持有鎖。
  2. 活力屬性:無死鎖。最終,即便鎖定資源的客戶端崩潰或分區,也始終能夠得到鎖。
  3. 活動性:容錯能力。只要大多數Redis節點都處於運行狀態,客戶端就能夠獲取和釋放鎖。

2、redis多節點實現分佈式鎖帶來的挑戰

咱們使用Redis鎖定資源的最簡單方法是:redis

  1. 在實例中建立鎖。
  2. 鎖一般使用Redis過時功能在有限時間存在,所以最終將被釋放,最終超過給按期限會被刪除。
  3. 當客戶端須要釋放資源時,它將刪除鎖。

乍一看,彷佛並無什麼問題。可是不妨咱們深究一下,這種實現方案在redis單機環境下彷佛並無什麼問題!可是若是節點宕了呢?好吧,那麼讓咱們添加一個slave節點!若是主服務器宕機了,就使用這個節點!可是咱們不妨來看看她真的能保證可用嗎?算法

在談論這個的致命缺陷時,咱們須要瞭解一個知識點,Redis複製是異步的。數據庫

  1. 客戶端A獲取主服務器中的鎖。
  2. 在將鎖複製傳輸到從機以前,主機崩潰。
  3. slave晉升爲 master
  4. 客戶端B獲取鎖,由於從機並無該鎖的對象,獲取成功!

顯然,這樣是不對的,主節點由於沒來得及同步數據就宕機了,因此從節點沒有該數據,從而形成分佈式鎖的失效,那麼做者antirez的觀點是如何解決這個呢?安全

3、Redlock算法

做者認爲,咱們應該使用多個Redis,這些節點是徹底獨立的,不須要使用複製或者任何協調數據的系統,多個redis系統獲取鎖的過程就變成了以下步驟:服務器

  1. 以毫秒爲單位獲取當前的服務器時間
  2. 嘗試使用相同的key和隨機值來獲取鎖,對每個機器獲取鎖時都應該有一個超時時間,好比鎖的過時時間爲10s那麼獲取單個節點鎖的超時時間就應該爲5到50毫秒左右,他這樣作的目的是爲了保證客戶端與故障的機器鏈接,耗費多餘的時間!超時間時間內未獲取數據就放棄該節點,從而去下一個節點獲取,直至將全部節點所有獲取一遍!
  3. 獲取完成後,獲取當前時間減去步驟一獲取的時間,當且僅當客戶端半數以上獲取成功且獲取鎖的時間小於鎖額超時時間,則證實該鎖生效!
  4. 獲取鎖以後,鎖的超時時間等於 設置的有效時間-獲取鎖花費的時間
  5. 若是 獲取鎖的機器不知足半數以上,或者鎖的超時時間計算完畢後爲負數 等異常操做,則系統會嘗試解鎖全部實例,即便有些實例沒有獲取鎖成功,依舊會被嘗試解鎖!
  6. 釋放鎖,只需在全部實例中釋放鎖,不管客戶端是否定爲它可以成功鎖定給定的實例。

4、可是Redlock真可以解決問題嗎?

Martin Kleppmann發表文章任務,Redlock並不能保證該鎖的安全性!微信

他認爲鎖的用途無非兩種網絡

  1. 提高效率,用鎖來保證一個任務沒有必要被執行兩次。好比(很昂貴的計算)
  2. 保證正確,使用鎖來保證任務按照正常的步驟執行,防止兩個節點同時操做一份數據,形成文件衝突,數據丟失。

對於第一種緣由,咱們對鎖是有必定寬容度的,就算髮生了兩個節點同時工做,對系統的影響也僅僅是多付出了一些計算的成本,沒什麼額外的影響。這個時候 使用單點的 Redis 就能很好的解決問題,沒有必要使用RedLock,維護那麼多的Redis實例,提高系統的維護成本。app

1.分佈式鎖的超時性,所帶來的缺點

可是對於第二種場景來講,就比較慎重了,由於極可能涉及到一些金錢交易,若是鎖定失敗,而且兩個節點同時處理同一數據,則結果將致使文件損壞,數據丟失,永久性不一致,或者金錢方面的損失!

咱們假設一種場景,咱們有兩個客戶端,每個客戶端必須拿到鎖以後才能去保存數據到數據庫,咱們使用RedLock算法實現會出現什麼問題呢?RedLock中,爲了防止死鎖,鎖是具備過時時間的,可是Martin 認爲這是不安全的!該流程圖相似於這樣!

客戶端1獲取到鎖成功後,開始執行,執行到一半系統發生Full GC ,系統服務被掛起,過段時間鎖超時了。

客戶端2等待客戶端1的鎖超時後,成功的獲取到鎖,開始執行入庫操做,完成後,客戶端1完成了Full GC,又作了一次入庫操做!這是不安全的!如何解決呢?

Martin 提出來一種相似樂觀鎖的實現機制,示例圖以下:

客戶端1長時間被掛起後,客戶端2獲取到鎖,開始寫庫操做,同時攜帶令牌 34,寫庫完成後,客戶端1甦醒,開始進行入庫操做,可是由於攜帶的令牌爲33 小於最新令牌,該次提交就被拒絕!

這個想法聽起來彷佛時很完備的思路,這樣即便系統由於某些緣由被掛起,數據也可以被正確的處理。可是仔細想一下:

  • 若是僅當您的令牌大於全部過去的令牌時,數據存儲區才能始終接受寫入,則它是可線性化的存儲區,至關與使用數據庫來實現一個 分佈式鎖系統,那麼RedLock的做用就變的微乎其微!甚至不在須要使用redis保證分佈式鎖!

2.RedLock對於系統時鐘強依賴

回想一下Redlock算法獲取鎖的幾個步驟,你會發現鎖的有效性是與當前的系統時鐘強依賴,咱們假設:

咱們有,A B C D E 五個redis節點:

  1. 客戶端1獲取節點A,B,C的鎖定。因爲網絡問題,沒法訪問D和E。
  2. 節點C上的時鐘向前跳,致使鎖過時。
  3. 客戶端2獲取節點C,D,E的鎖定。因爲網絡問題,沒法訪問A和B。
  4. 如今,客戶1和2都認爲他們持有該鎖。

若是C在將鎖持久保存到磁盤以前崩潰並當即從新啓動,則可能會發生相似的問題。

Martin認爲系統時間的階躍主要來自兩個方面(以及做者給出的解決方案):

  1. 人爲修改。
    • 對於人爲修改,能說啥呢?人要搞破壞沒辦法避免。
  2. 從NTP服務收到了一個跳躍時時鐘更新。
    • NTP受到一個階躍時鐘更新,對於這個問題,須要經過運維來保證。須要將階躍的時間更新到服務器的時候,應當採起小步快跑的方式。屢次修改,每次更新時間儘可能小。

3.基於程序語言彌補分佈式鎖的超時性所帶來的缺點

咱們回顧 1 觀點,深究抽象出現這個缺陷的根本緣由,就是爲了解決因爲系統宕機帶來的鎖失效而給鎖強加了一個失效時間,異常狀況下,程序(業務)執行的時間大於鎖失效時間從而形成的一系列的問題,咱們可否從這方面去考慮,從而用程序來解決這個樣一個死局 呢?

既然是由於鎖的失效時間小於業務時間,那麼咱們想辦法保證業務程序執行時間絕對小於鎖超時時間不久解決了?

java語言中redisson實現了一種保證鎖失效時間絕對大於業務程序執行時間的機制。官方叫作看門狗機制(Watchdog),他的主要原理是,在程序成功獲取鎖以後,會fork一條子線程去不斷的給該鎖續期,直至該鎖釋放爲止!他的原理圖大概以下所示:

redisson使用守護線程來進行鎖的續期,(守護線程的做用:當主線程銷燬,會和主線程一塊兒銷燬。)防止程序宕機後,線程依舊不斷續命,形成死鎖!

另外,Redisson還實現而且優化了 RedLock算法、公平鎖、可重入鎖、連鎖等操做,使Redis分佈式鎖的實現方式更加簡便高效!



才疏學淺,若是文章中理解有誤,歡迎大佬們私聊指正!歡迎關注做者的公衆號,一塊兒進步,一塊兒學習!



本文分享自微信公衆號 - JAVA程序狗(javacxg)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索