基於Redis的分佈式鎖

Redis單點方式:html

首先,Redis客戶端爲了獲取鎖,向Redis節點發送以下命令:node

SET resource_name my_random_value NX PX 30000

上面的命令若是執行成功,則客戶端成功獲取到了鎖,接下來就能夠訪問共享資源了;而若是上面的命令執行失敗,則說明獲取鎖失敗。redis

注意,在上面的SET命令中:算法

  • my_random_value是由客戶端生成的一個隨機字符串,它要保證在足夠長的一段時間內在全部客戶端的全部獲取鎖的請求中都是惟一的。
  • NX表示只有當resource_name對應的key值不存在的時候才能SET成功。這保證了只有第一個請求的客戶端才能得到鎖,而其它客戶端在鎖被釋放以前都沒法得到鎖。
  • PX 30000表示這個鎖有一個30秒的自動過時時間。固然,這裏30秒只是一個例子,客戶端能夠選擇合適的過時時間。

最後,當客戶端完成了對共享資源的操做以後,執行下面的Redis Lua腳原本釋放鎖數據庫

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

這段Lua腳本在執行的時候要把前面的my_random_value做爲ARGV[1]的值傳進去,把resource_name做爲KEYS[1]的值傳進去。服務器

總結:dom

1.必須設置過時時間,過時時間須要謹慎考慮,過短鎖沒等主動釋放就過時,太長一旦擁有鎖的對象出現問題,別人會很長時間沒法使用。異步

2.setnx 和 過時時間必須是原子的分佈式

3.value必須是隨機的,保證解鈴還須繫鈴人,別人解不了post

4.釋放必須用Lua原子方式進行。由於Get和Del是分佈進行的,沒髮指定刪除隨機value的key,因此必須原子性,防止刪除的時候不是本身的鎖,而是過時後別人加上去的鎖。

另外:單機的問題沒法達到高可用,當redis宕了後,鎖失效,經過slave方式處理也可能會有1s內的數據同步問題,若是正好遇上master上加上鎖,還沒同步到slave上,master宕,slave變成master時鎖不對。

 

多redis集羣方式:(客戶端須要作的事情太多,不太好用仍是用zk吧)

  1. 獲取當前時間。
  2. 完成獲取鎖的整個過程(與N個Redis節點交互)。
  3. 再次獲取當前時間。
  4. 把兩個時間相減,計算獲取鎖的過程是否消耗了太長時間,致使鎖已通過期了。若是沒過時,
  5. 客戶端持有鎖去訪問共享資源。

 

考慮:若是Client1 獲取lock1後,執行了Full GC,到了過時時間,lock1過時。Client2獲取lock2,執行數據操做。Client1恢復運行,執行數據操做!這樣兩個Client同時拿到了鎖。

可讓Lock Server在分配鎖的時候返回一個遞增數字,在執行數據操做的時候,須要提交這個數字,記錄最後一次執行的數字,而且只接受比這個數字大的請求。保證請求數據操做的Client是按拿到鎖的順序執行的。但redis暫時不能返回這個數字。

 

使用分佈式鎖的方式有兩種狀況:

  • 爲了效率(efficiency),協調各個客戶端避免作重複的工做。即便鎖偶爾失效了,只是可能把某些操做多作一遍而已,不會產生其它的不良後果。好比重複發送了一封一樣的email。
  • 爲了正確性(correctness)。在任何狀況下都不容許鎖失效的狀況發生,由於一旦發生,就可能意味着數據不一致(inconsistency),數據丟失,文件損壞,或者其它嚴重的問題。

結論:

  • 若是是爲了效率(efficiency)而使用分佈式鎖,容許鎖的偶爾失效,那麼使用單Redis節點的鎖方案就足夠了,簡單並且效率高。Redlock則是個太重的實現(heavyweight)。
  • 若是是爲了正確性(correctness)在很嚴肅的場合使用分佈式鎖,那麼不要使用Redlock。它不是創建在異步模型上的一個足夠強的算法,它對於系統模型的假設中包含不少危險的成分(對於timing)。並且,它沒有一個機制可以提供fencing token。那應該使用什麼技術呢?Martin認爲,應該考慮相似Zookeeper的方案,或者支持事務的數據庫。

 

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

 

一個基於ZooKeeper構建分佈式鎖的描述(固然這不是惟一的方式):

  • 客戶端嘗試建立一個znode節點,好比/lock。那麼第一個客戶端就建立成功了,至關於拿到了鎖;而其它的客戶端會建立失敗(znode已存在),獲取鎖失敗。
  • 持有鎖的客戶端訪問共享資源完成後,將znode刪掉,這樣其它客戶端接下來就能來獲取鎖了。
  • znode應該被建立成ephemeral的。這是znode的一個特性,它保證若是建立znode的那個客戶端崩潰了,那麼相應的znode會被自動刪除。這保證了鎖必定會被釋放。

看起來這個鎖至關完美,沒有Redlock過時時間的問題,並且能在須要的時候讓鎖自動釋放。但仔細考察的話,並不盡然。

ZooKeeper是怎麼檢測出某個客戶端已經崩潰了呢?實際上,每一個客戶端都與ZooKeeper的某臺服務器維護着一個Session,這個Session依賴按期的心跳(heartbeat)來維持。若是ZooKeeper長時間收不到客戶端的心跳(這個時間稱爲Sesion的過時時間),那麼它就認爲Session過時了,經過這個Session所建立的全部的ephemeral類型的znode節點都會被自動刪除。

設想以下的執行序列:

  1. 客戶端1建立了znode節點/lock,得到了鎖。
  2. 客戶端1進入了長時間的GC pause。
  3. 客戶端1鏈接到ZooKeeper的Session過時了。znode節點/lock被自動刪除。
  4. 客戶端2建立了znode節點/lock,從而得到了鎖。
  5. 客戶端1從GC pause中恢復過來,它仍然認爲本身持有鎖。

最後,客戶端1和客戶端2都認爲本身持有了鎖,衝突了。這與以前Martin在文章中描述的因爲GC pause致使的分佈式鎖失效的狀況相似。

ZooKeeper的watch機制。這個機制能夠這樣來使用,好比當客戶端試圖建立/lock的時候,發現它已經存在了,這時候建立失敗,但客戶端不必定就此對外宣告獲取鎖失敗。客戶端能夠進入一種等待狀態,等待當/lock節點被刪除的時候,ZooKeeper經過watch機制通知它,這樣它就能夠繼續完成建立操做(獲取鎖)。這可讓分佈式鎖在客戶端用起來就像一個本地的鎖同樣:加鎖失敗就阻塞住,直到獲取到鎖爲止。這樣的特性Redlock就沒法實現。

小結一下,基於ZooKeeper的鎖和基於Redis的鎖相比在實現特性上有兩個不一樣:

  • 在正常狀況下,客戶端能夠持有鎖任意長的時間,這能夠確保它作完全部須要的資源訪問操做以後再釋放鎖。這避免了基於Redis的鎖對於有效時間(lock validity time)到底設置多長的兩難問題。實際上,基於ZooKeeper的鎖是依靠Session(心跳)來維持鎖的持有狀態的,而Redis不支持Sesion。
  • 基於ZooKeeper的鎖支持在獲取鎖失敗以後等待鎖從新釋放的事件。這讓客戶端對鎖的使用更加靈活。

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

相關文章
相關標籤/搜索