Redis分布式鎖並不適合項目應用(打臉)

昨天,分享了一篇Redis實現分佈式鎖服務的文章,有單節點的分佈式鎖實現和紅鎖的實現兩種。大家都知道單節點存在單點故障的問題,所以引入了紅鎖的實現。但紅鎖真的就可靠嗎?這個問題,本應該在昨天文章中提出的,這裡先打臉,給大家道歉。今天我們過一遍Martin的文章,分析基於Redis實現的分佈式鎖服務到底有哪些問題。

Redis分佈式鎖並不適合項目應用(打臉)

目錄:

  1. 我們使用鎖的目的

  2. 用鎖保護共享資源

  3. 具有版本的鎖服務

  4. 紅鎖不可靠的示例

1. 我們使用鎖的目的

併發操作共同的資源容易引起數據更新丟失,導致髒讀、數據不一致,所以需要使用鎖保護起來。應用中,我們把目標的實現定義為兩種:一種是可以容忍概率極低的稍許錯誤,因為錯誤的代價可以忽略;另外一種是絕對可靠,不容許任何錯誤,任何錯誤都是致命的。在搞清楚目的之後,選擇鎖的實現上,就可以有效做出取捨。如果可已容忍稍許錯誤,選擇Redis實現的鎖完全沒有問題。否則,不建議使用。下面說明一下為什麼不建議使用。

2. 用鎖保護共享資源

拋開紅鎖的算法,我們先看一個例子:文件併發讀寫問題,首先有一個client獲得了鎖,讀取並修改了文件,然後寫到存儲系統中,並最後釋放了鎖。該鎖防止其他client併發修改,代碼示例:

Redis分佈式鎖並不適合項目應用(打臉)

以上代碼看上去沒有什麼問題,但是在一個分佈式系統中,以上代碼很可能導致數據更新丟失,導致數據損壞:

Redis分佈式鎖並不適合項目應用(打臉)

  1. client1獲得了鎖,過程中JVM垃圾回收,線程阻塞導致鎖超時;

  2. client2獲得鎖,更新完成寫操作,釋放鎖。

  3. client1線程恢復,寫到存儲。

以上發生了同一時間,有兩個線程同時獲得鎖,導致數據更新丟失。在JAVA代碼中,垃圾回收是不可預知的,隨時都可能發生。即使我們不使用JAVA編程,排除垃圾回收的問題,那麼我們仍然不可預知是否存在其他因素導致線程暫停,例如:從硬盤加載數據到內存、網絡擁塞期間、報文網絡傳輸包丟失、CPU時間分片期間、操作系統中斷等等因素,都會導致程序線程被阻塞暫停,因此我們不可預知客戶端是否能在鎖超時之前完成更新操作。

3. 具有版本的鎖服務

要想實現一個可靠的分佈式鎖服務實際上很簡單,我們在完成寫操作的時候,帶上讀取時的版本號作為更新條件。該版本號可以是遞增序列,在獲得鎖的時候+1。

Redis分佈式鎖並不適合項目應用(打臉)

  1. Client1在獲得鎖的時候,分配了一個token=33,之後線程暫停直至鎖超時。

  2. Client2獲得鎖,分配了token=34,完成更新,成功落盤,然後結合token=34成功落盤。

  3. Client1恢復,寫盤發現token=33已經失效,鎖服務拋異常,拒絕本次提交。

然而RedLock算法並不具備生成類似版本號的功能,即便算法再怎麼完備,也不能防止獲得資源鎖的線程被其他不可預知的因素阻塞,從而導致鎖超時。

4. 紅鎖不可靠的示例

分析下為什麼RedLock不可靠:依賴Redis時間的鎖不可靠(clock存在時間跳變)。

示例:5個Redis節點,2個client,網絡跳變導致鎖失效。

  1. Client1獲得A、B、C鎖,因為網絡問題,D、E不可達。

  2. 時鐘跳變,導致C鎖超時。

  3. Client2獲得C、D、E鎖,因為網絡問題,A、B不可達。

  4. Client1和Client2同時持有相同的鎖。

即使我們假設不存在時間跳變,完美的NTP校時服務,依然會有問題:

  1. Client1請求節點A、B、C、D、E的鎖。

  2. Client1在收到請求返回之前,線程暫停了。

  3. Client1在節點上的鎖過期了。

  4. Client2獲得了A、B、C、D、E上的鎖。

  5. Client1恢復並收到了A、B、C、D、E的回覆內容(Socket Rec Buffer內的緩存),成功獲得鎖。

  6. Client1和Client2同時持有相同的鎖。

分析下為什麼RedLock不可靠:基於同步編程模型的RedLock不可靠。

使用同步的編程模型,我們必須有以下假定:假設有可控的網絡延遲、假設有可控的線程調度、假設有可控的時鐘異常。

只有在以上假定的條件下,我們使用紅鎖才可以使用補償的方式解決網絡延遲、時鐘漂移、線程調度的問題。紅鎖假定了網絡延遲、時鐘漂移以及線程調度的時間非常短,遠小於鎖超時時間。一旦補償的時間和鎖過期時間相同了,紅鎖算法就會失效。

在理想的環境中,大多數情況下是可以滿足的,也正是因此我們產生了大多數情況下滿足條件的一致性算法,例如Zab和Paxos。

總結:

  1. 不建議使用紅鎖,實現起來重,而且不可靠。

  2. 建議使用基於版本的分佈式鎖實現,例如使用ZK、Curator、甚至使用數據的事務實現鎖。

  3. 如果對鎖的要求不是那麼嚴格,完全可以使用單節點Redis實現的鎖。

  4. 昨天有網友提出很不錯,本文就有作者明確的立場:請發揮Redis的優勢,而非短板。

  5. 我們馬上玩轉ZK,別急。

大家有想學習的東西,不妨在評論中提出,希望能幫到大家,給大家帶來收穫。


分享到:


相關文章: