首先要清楚,分佈式鎖的實現主要需要涉及到那幾點,這裏無非就是加鎖和解鎖。
後面要談的就是怎麼來實現,這其中又涉及到了redis的原子性以及使用lua腳本來操作的方法,進一步比較了lua腳本與redis事務的優缺點
最後談到了php中如果調用lua腳本實現的問題
分佈式鎖的特點(也即說明吧)
分佈式鎖一般有如下的特點:
- 互斥性: 同一時刻只能有一個線程持有鎖
- 可重入性: 同一節點上的同一個線程如果獲取了鎖之後能夠再次獲取鎖
- 鎖超時:和J.U.C中的鎖一樣支持鎖超時,防止死鎖
- 高性能和高可用: 加鎖和解鎖需要高效,同時也需要保證高可用,防止分佈式鎖失效
- 具備阻塞和非阻塞性:能夠及時從阻塞狀態中被喚醒
分佈式鎖的實現方式
我們一般實現分佈式鎖有以下幾種方式:
- 基於數據庫
- 基於Redis
- 基於zookeeper
本篇文章主要介紹基於Redis如何實現分佈式鎖
1.利用redis實現分佈式鎖:【分佈式緩存系列】Redis實現分佈式鎖的正確姿勢(實現看這篇就好了)
主要點是:要使用Redis實現分佈式鎖。加鎖操作的正確姿勢爲:
- 使用setnx命令保證互斥性
- 需要設置鎖的過期時間,避免死鎖
- setnx和設置過期時間需要保持原子性,避免在設置setnx成功之後在設置過期時間客戶端崩潰導致死鎖
- 加鎖的Value 值爲一個唯一標示。可以採用UUID作爲唯一標示。加鎖成功後需要把唯一標示返回給客戶端來用來客戶端進行解鎖操作
解鎖的正確姿勢爲:
1. 需要拿加鎖成功的唯一標示要進行解鎖,從而保證加鎖和解鎖的是同一個客戶端
2. 解鎖操作需要比較唯一標示是否相等,相等再執行刪除操作。這2個操作可以採用Lua腳本方式使2個命令的原子性。
加鎖通過setnx(即把是否存在以及設置過期時間統一通過setnx這個原子性來完成);釋放鎖因爲涉及到要判斷是否是同一個客戶端,所以還有先判斷再釋放,這個需要使用lua的原子性來進行操作
redis的事務與lua腳本的對比
一.原理
1.redis事務
基本原理爲樂觀鎖,多個client對操作的key進行watch,一旦有一個client進行了exec,那麼其它client的exec就會失效。其實現原理可參考 Redis watch機制的分析。
2.lua腳本
基本原理爲使腳本相當於一個redis命令,可以結合redis原有命令,自定義腳本邏輯。
使用Lua腳本的好處:
- 減少網絡開銷。可以將多個請求通過腳本的形式一次發送,減少網絡時延。
- 原子操作。redis會將整個腳本作爲一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
- 複用。客戶端發送的腳本會永久存在redis中,這樣,其他客戶端可以複用這一腳本而不需要使用代碼完成相同的邏輯。
3.兩者異同
相同點
很好的實現了一致性、隔離性和持久性,但沒有實現原子性,無論是redis事務,還是lua腳本,如果執行期間出現運行錯誤,之前的執行過的命令是不會回滾的。
不同點
(1)redis事務是基於樂觀鎖,lua腳本是基於redis的單線程執行命令。
(2)redis事務的執行原理就是一次 命令的批量執行,而lua腳本可以加入自定義邏輯。
個人總結:其實事務的操作實際上是利用了事務+watch實現原子操作,但是不免有點太僵硬,很明顯這是一個if……else語句,爲什麼這麼說呢,因爲redis的事務是一種樂觀鎖的實現,事務A在進行操作key1時,然後執行exec,當事務B也在操作key2時,發現key2被exec了,那麼事務B此時執行失敗,不執行相關事務。
其他:
另外,php中使用lua的用法:
$redis->eval($lua, ['key1','key2','first','second'], 2)
在redis的lua解釋器裏面對應的執行的命令如下:
eval “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 2 key1 key2 first second
解釋: “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是被求值的 Lua 腳本,數字 2 指定了鍵名參數的數量, key1 和 key2 是鍵名參數,分別使用 KEYS[1] 和 KEYS[2] 訪問,而最後的 first 和 second 則是附加參數,可以通過 ARGV[1] 和 ARGV[2] 訪問它們。
PHP中使用redis拓展執行腳本時,eval方法的參數 3個,第一個是腳本代碼,第二個是一個數組,參數數組,第三個參數是個整數,表示第二個參數中的前幾個鍵名參數,剩下的都是附加參數
記住一點,lua總共就四個參數,非常簡單
<1> script: lua腳本,如果是文本路徑,需要加file_get_contens()來獲取
<2> numkeys: key的個數
<3> key: redis中各種數據結構的替代符號
<4> arg: 你的自定義參數
參考: