要使用 php 和 redis 實現(xiàn)分布式鎖,核心在于確保并發(fā)環(huán)境下對共享資源的安全訪問。1. 使用 redis 的 set 命令加鎖,并帶上 nx 和 px 參數(shù),保證操作原子性且設(shè)置過期時間以避免死鎖;2. 釋放鎖時需先驗證鎖的擁有者,推薦通過 lua 腳本實現(xiàn)刪除操作,確保安全性;3. 設(shè)置合理的超時時間(ttl),根據(jù)業(yè)務(wù)預(yù)估任務(wù)執(zhí)行時間并預(yù)留緩沖,防止鎖提前釋放或阻塞后續(xù)請求;4. 高可用場景下可考慮 redlock 算法提升可靠性,但多數(shù)情況下單實例已足夠。以上步驟共同保障了分布式鎖的有效性和安全性。
在使用 PHP 和 redis 實現(xiàn)分布式鎖時,核心目標(biāo)是確保多個服務(wù)或節(jié)點在并發(fā)訪問共享資源時,能安全地協(xié)調(diào)操作。Redis 作為內(nèi)存數(shù)據(jù)庫,天然適合用作分布式鎖的實現(xiàn)工具,而 PHP 結(jié)合 Redis 的擴展(如 PhpRedis 或 Predis)可以輕松完成這項任務(wù)。
下面介紹幾個關(guān)鍵步驟和注意事項,幫助你正確實現(xiàn)一個可靠的分布式鎖。
1. 使用 SET 命令加鎖,保證原子性
最基礎(chǔ)的加鎖方式是通過 Redis 的 SET 命令,并加上 NX 和 PX 參數(shù):
立即學(xué)習(xí)“PHP免費學(xué)習(xí)筆記(深入)”;
- NX 表示只有 key 不存在時才設(shè)置成功
- PX 設(shè)置過期時間,單位是毫秒
這樣做的好處是可以避免死鎖,同時整個操作是原子的,不會出現(xiàn)中間狀態(tài)。
示例:
$lockKey = 'lock:job_123'; $lockValue = uniqid(); // 唯一標(biāo)識,用于后續(xù)釋放鎖 $ttl = 5000; // 鎖的過期時間,單位毫秒 $redis->set($lockKey, $lockValue, ['nx', 'px' => $ttl]);
如果返回 false,說明鎖已經(jīng)被別人占用了,當(dāng)前請求需要等待或跳過。
2. 釋放鎖要小心,只能刪自己的
釋放鎖時不能直接刪除 key,否則可能誤刪其他線程持有的鎖。正確的做法是先判斷鎖的值是否匹配,再刪除。
這個操作推薦使用 Lua 腳本來執(zhí)行,保證原子性。
Lua 示例:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
PHP 調(diào)用方式:
$script = <<eval($script, [$lockKey, $lockValue], 1);
如果返回 1 表示釋放成功,0 表示不是自己加的鎖,不處理。
3. 設(shè)置合理的超時時間,避免業(yè)務(wù)阻塞
鎖的過期時間要根據(jù)業(yè)務(wù)邏輯來定。比如一段任務(wù)預(yù)計執(zhí)行 3 秒,那鎖的 TTL 可以設(shè)為 5 秒,防止因為網(wǎng)絡(luò)問題或程序卡頓導(dǎo)致鎖一直不釋放。
但也要注意:
- 如果任務(wù)執(zhí)行時間太長,鎖可能會提前釋放,造成并發(fā)風(fēng)險
- 可以考慮“看門狗”機制自動續(xù)租(比如 Redlock 算法中的一種做法),不過實現(xiàn)起來復(fù)雜度較高
一般情況下,建議:
- 將任務(wù)拆小
- 控制單個任務(wù)執(zhí)行時間
- 加鎖前預(yù)估好耗時
4. 可選:使用 Redlock 算法提升可靠性(適用于高可用場景)
如果你部署了多個 Redis 實例,可以使用 Redis 官方推薦的 Redlock 算法來提高鎖的容錯能力。
它的基本思路是:
- 同時向多個獨立的 Redis 實例申請鎖
- 成功超過半數(shù)才算加鎖成功
- 鎖的有效時間取最小的那個剩余時間
不過對于大多數(shù)中小型項目來說,單實例 + 上述方法已經(jīng)足夠。
基本上就這些。實現(xiàn)一個簡單的分布式鎖并不難,但要注意細(xì)節(jié),比如鎖的唯一性、釋放的安全性和合理設(shè)置過期時間,否則容易引發(fā)死鎖或并發(fā)沖突。