redis 的redLock

Redis 官方站這篇文章提出了一種權威的基於 Redis 實現分佈式鎖的方式名叫 Redlock,此種方式比原先的單節點的方法更安全。它可以保證以下特性:

安全特性:互斥訪問,即永遠只有一個 client 能拿到鎖
避免死鎖:最終 client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的 client crash 了或者出現了網絡分區
容錯性:只要大部分 Redis 節點存活就可以正常提供服務
怎麼在單節點上實現分佈式鎖
SET resource_name my_random_value NX PX 30000

主要依靠上述命令,該命令僅當 Key 不存在時(NX保證)set 值,並且設置過期時間 3000ms (PX保證),值 my_random_value 必須是所有 client 和所有鎖請求發生期間唯一的,釋放鎖的邏輯是:

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

上述實現可以避免釋放另一個client創建的鎖,如果只有 del 命令的話,那麼如果 client1 拿到 lock1 之後因爲某些操作阻塞了很長時間,此時 Redis 端 lock1 已經過期了並且已經被重新分配給了 client2,那麼 client1 此時再去釋放這把鎖就會造成 client2 原本獲取到的鎖被 client1 無故釋放了,但現在爲每個 client 分配一個 unique 的 string 值可以避免這個問題。至於如何去生成這個 unique string,方法很多隨意選擇一種就行了。

Redlock 算法
算法很易懂,起 5 個 master 節點,分佈在不同的機房儘量保證可用性。爲了獲得鎖,client 會進行如下操作:

得到當前的時間,微妙單位
嘗試順序地在 5 個實例上申請鎖,當然需要使用相同的 key 和 random value,這裏一個 client 需要合理設置與 master 節點溝通的 timeout 大小,避免長時間和一個 fail 了的節點浪費時間
當 client 在大於等於 3 個 master 上成功申請到鎖的時候,且它會計算申請鎖消耗了多少時間,這部分消耗的時間採用獲得鎖的當下時間減去第一步獲得的時間戳得到,如果鎖的持續時長(lock validity time)比流逝的時間多的話,那麼鎖就真正獲取到了。
如果鎖申請到了,那麼鎖真正的 lock validity time 應該是 origin(lock validity time) - 申請鎖期間流逝的時間
如果 client 申請鎖失敗了,那麼它就會在少部分申請成功鎖的 master 節點上執行釋放鎖的操作,重置狀態
失敗重試
如果一個 client 申請鎖失敗了,那麼它需要稍等一會在重試避免多個 client 同時申請鎖的情況,最好的情況是一個 client 需要幾乎同時向 5 個 master 發起鎖申請。另外就是如果 client 申請鎖失敗了它需要儘快在它曾經申請到鎖的 master 上執行 unlock 操作,便於其他 client 獲得這把鎖,避免這些鎖過期造成的時間浪費,當然如果這時候網絡分區使得 client 無法聯繫上這些 master,那麼這種浪費就是不得不付出的代價了。

放鎖
放鎖操作很簡單,就是依次釋放所有節點上的鎖就行了

性能、崩潰恢復和 fsync
如果我們的節點沒有持久化機制,client 從 5 個 master 中的 3 個處獲得了鎖,然後其中一個重啓了,這是注意 整個環境中又出現了 3 個 master 可供另一個 client 申請同一把鎖! 違反了互斥性。如果我們開啓了 AOF 持久化那麼情況會稍微好轉一些,因爲 Redis 的過期機制是語義層面實現的,所以在 server 掛了的時候時間依舊在流逝,重啓之後鎖狀態不會受到污染。但是考慮斷電之後呢,AOF部分命令沒來得及刷回磁盤直接丟失了,除非我們配置刷回策略爲 fsnyc = always,但這會損傷性能。解決這個問題的方法是,當一個節點重啓之後,我們規定在 max TTL 期間它是不可用的,這樣它就不會干擾原本已經申請到的鎖,等到它 crash 前的那部分鎖都過期了,環境不存在歷史鎖了,那麼再把這個節點加進來正常工作。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章