1. 程式人生 > >用redis構建分布式鎖

用redis構建分布式鎖

宕機 阻塞 second 計算 中一 超時 mas red 個數

原文:用redis構建分布式鎖

用redis構建分布式鎖

單實例的實現

從2.6.12版本開始,redis為SET命令增加了一系列選項:

  • EX seconds – 設置鍵key的過期時間,單位時秒
  • PX milliseconds – 設置鍵key的過期時間,單位時毫秒
  • NX – 只有鍵key不存在的時候才會設置key的值
  • XX – 只有鍵key存在的時候才會設置key的值

如果有2個進程(可能位於不同機器)需要競爭某個資源,可以為這個資源加鎖,鎖放在redis裏面,這樣兩個進程都能訪問到,例如下面的命令:

SET resource-name random-value NX EX max-lock-time

僅當key不存在時,設置一個鍵值對,並且設置了key的過期時間。

如果其中一個進程set成功,那麽另外一個進程會set失敗,只要判斷set命令的返回值,就可以判斷是否加鎖成功。

這裏resouce-name是需要加鎖的資源,而random-value每個進程都可以寫唯一值,而max-lock-time是鎖的最大持有時間。

如何釋放鎖:

a客戶端獲得的鎖(鍵key)已經由於過期時間到了被redis服務器刪除,但是這個時候a客戶端還去執行DEL命令。而b客戶端已經在a設置的過期時間之後重新獲取了這個同樣key的鎖,那麽a執行DEL就會釋放了b客戶端加好的鎖。

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

由於每個進程寫入的value是自己生成的隨機數,可以保證一個進程只能刪除自己加的鎖,而避免誤刪其它進程加的鎖。

分布式鎖

在分布式版本的算法裏我們假設我們有N個Redis master節點,這些節點都是完全獨立的,我們不用任何復制或者其他隱含的分布式協調算法。我們已經描述了如何在單節點環境下安全地獲取和釋放鎖。因此我們理所當然地應當用這個方法在每個單節點裏來獲取和釋放鎖。在我們的例子裏面我們把N設成5,這個數字是一個相對比較合理的數值,因此我們需要在不同的計算機或者虛擬機上運行5個master節點來保證他們大多數情況下都不會同時宕機。一個客戶端需要做如下操作來獲取鎖:

1.獲取當前時間(單位是毫秒)。

2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10s,那每個節點鎖請求的超時時間可能是5~50ms的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該盡快嘗試下一個master節點。

3.客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裏是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了。

4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。

5.如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,一定要盡快在獲取鎖成功的節點上釋放鎖,這樣就沒必要等到key超時後才能重新獲取這個鎖(但是如果網絡分區的情況發生而且客戶端無法連接到Redis節點時,會損失等待key超時這段時間的系統可用性)。

註意:當一個客戶端獲取鎖失敗時,這個客戶端應該在一個隨機延時後進行重試,之所以采用隨機延時是為了避免不同客戶端同時重試導致誰都無法拿到鎖的情況出現。同樣的道理客戶端越快嘗試在大多數Redis節點獲取鎖,出現多個客戶端同時競爭鎖和重試的時間窗口越小,可能性就越低,所以最完美的情況下,客戶端應該用多路傳輸的方式同時向所有Redis節點發送SET命令。

參考文檔:

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

https://github.com/SPSCommerce/redlock-py

用redis構建分布式鎖