Last-Modified: 2019年6月5日15:59:34php
若是是Redis集羣, 還得考慮具備容錯性: 只要大部分Redis節點正常運行, 客戶端就能夠加鎖和解鎖.git
如下只考慮 Redis單機部署的 場景.github
若是是Redis集羣部署, 可使用redis
php 加鎖示例算法
$redis = new Redis(); $redis->pconnect("127.0.0.1", 6379); $redis->auth("password"); // 密碼驗證 $redis->select(1); // 選擇所使用的數據庫, 默認有16個 $key = "..."; $value = "..."; $expire = 3; // 參數解釋 ↓ // $value 加鎖的客戶端請求標識, 必須保證在全部獲取鎖清秋的客戶端裏保持惟一, 知足上面的第3個條件: 加鎖/解鎖的是同一客戶端 // "NX" 僅在key不存在時加鎖, 知足條件1: 互斥型 // "EX" 設置鎖過時時間, 知足條件2: 避免死鎖 $redis->set($key, $value, ["NX", "EX" => $expire])
執行上面代碼結果:數據庫
加鎖容易錯誤的點:緩存
setnx
和 expire
的組合緣由: 若在 setnx
後腳本崩潰會致使死鎖服務器
$value
客戶端標識的:分佈式
在php中, 若使用 pconnect
鏈接redis, 則在當前腳本聲明週期結束後, 與redis創建的鏈接仍會保留, 直到對應fpm進程的生命週期結束, 同時在下一次請求時, fpm會重用該鏈接.ide
即該鏈接的生命週期是 fpm 進程的生命週期, 而非一次php腳本的執行.
若代碼使用 pconnect
, close
的做用僅是使當前php腳本不能再進行redis請求, 並無真正關閉與redis的鏈接, 鏈接在後續請求中仍然會被重用.
pconnect函數在線程版本中不能被使用
上圖中, php-fpm 與redis創建的鏈接並未隨請求結束後立刻斷開
php解鎖示例: 使用lua腳本
$key = "..."; $identification = "..."; // KEYS 和 ARGV 是lua腳本中的全局變量 $script = <<< EOF if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end EOF; # $result = $redis->eval($script, [$key, $identification], 1); // 返回結果 >0 表示解鎖成功 // php中參數的傳遞順序與標準不同, 注意區分 // 第2個參數表示傳入的 KEYS 和 ARGV, 經過第3個參數來區分, KEYS 在前, ARGV 在後 // 第3個參數表示傳入的 KEYS 的個數 $result = $redis->evaluate($script, [$key, $identification], 1);
使用Lua腳本的緣由:
避免誤刪其餘客戶端加的鎖
eg. 某個客戶端獲取鎖後作其餘操做太久致使鎖被自動釋放, 這時候要避免這個客戶端刪除已經被其餘客戶端獲取的鎖, 這就用到了鎖的標識.
get
和 del
是原子性的, 整個lua腳本會被當作一條命令來執行get
後鎖恰好過時, 此時也不會被其餘客戶端加鎖eval命令執行Lua代碼的時候,Lua代碼將被當成一個命令去執行,而且直到eval命令執行完成,Redis纔會執行其餘命令。因爲 script 執行的原子性, 因此不要在script中執行過長開銷的程序,不然會驗證影響其它請求的執行。
解鎖容易錯誤的點:
del
刪除鍵緣由: 可能移除掉其餘客戶端加的鎖(在本身的鎖已過時狀況下)
get
判斷鎖歸屬, 若符合再 del
緣由: 非原子性操做, 若在 get
後鎖過時了, 此時別的客戶端進行加鎖操做, 這裏的 del
就會錯誤的將其餘客戶端加的鎖解開.
↓ 這一段內容轉載自 https://blog.csdn.net/zhouzme...
注意點:
義變量必定要使用局部變量, 即 local var = 1
, 局部變量只在所定義的塊(指控制結構, 函數或chunk等)內有效, 使用局部變量能夠避免命名衝突 而且訪問更快(lua中局部變量和全局變量存儲方式是不同的)
若是Lua腳本寫的比較長,非本地或局域網的狀況下,建議使用 SHA 簽名的方法來調用,這樣節省帶寬,但對性能彷佛沒什麼直接的提高。這裏對小白普及下我理解的原理就是 Redis 會把每一個腳本都生成惟一簽名,把腳本做爲函數體,並使用該簽名做爲腳本的函數名放到緩存中,因此後面調用就只須要傳一個 SHA 簽名就能夠調用該函數了,精簡不少了。同一個腳本生成的簽名都是相同的,因此SHA簽名能夠先在本地生成,而後在服務器上 script load 一次腳本,程序中只需保存和使用該簽名便可。另外須要注意的是,腳本若是被改動哪怕一個換行或一個空格(這些容易被忽略或誤操做)都必須從新 load 來獲取新的 SHA
注意:獲取 SHA 簽名是單獨的功能,不要放在你的正常流程中,當本地開發時就能夠生成SHA,把字符串寫死在流程中。一樣的腳本,Reids是始終生成相同的簽名的。
tonumber()
方法 if (tonumber(ARGV[1]) > 0) then return 1; end;
Redis 集羣相對單機來講, 須要考慮一個 容錯性, 設計上更爲複雜
因爲這個我也從未實踐過, 先貼一個官方的教程貼壓壓驚
https://github.com/antirez/re...
對應的翻譯: http://ifeve.com/redis-lock/
官方給出了一個 RedLock 算法
情景: 當前有N個徹底獨立的Redis master節點, 分別部署在不一樣的主機上
客戶端獲取鎖的操做: