在使用分布式鎖進(jìn)行互斥資源訪問(wèn)時(shí)候,我們很多方案是采用redis的實(shí)現(xiàn)。
固然,redis的單節(jié)點(diǎn)鎖在極端情況也是有問(wèn)題的,假設(shè)你的業(yè)務(wù)允許偶爾的失效,使用單節(jié)點(diǎn)的redis鎖方案就足夠了,簡(jiǎn)單而且效率高。
redis鎖失效的情況:
客戶(hù)端1從master節(jié)點(diǎn)獲取了鎖
master宕機(jī)了,存儲(chǔ)鎖的key還沒(méi)來(lái)得及同步到slave節(jié)點(diǎn)上
slave升級(jí)為master
客戶(hù)端2從新的master上獲取到同一個(gè)資源的鎖
于是,客戶(hù)端1和客戶(hù)端2同事持有了同一個(gè)資源的鎖,鎖的安全性被打破。
如果我們不考慮這種極端情況,需要實(shí)現(xiàn)一個(gè)基于單節(jié)點(diǎn)redis鎖的大致流程:
set cache_key random_seed NX PX 30000
上面這個(gè)set命令拆解開(kāi)就是:
setnx cache_key random_seed
expire cache_key 30
雖然這兩組命令執(zhí)行的效果一樣,但是第二個(gè)是非原子性操作,如果執(zhí)行了setnx成功,但是expire失敗的話(huà),就會(huì)造成這個(gè)key一直存在了,無(wú)法釋放的情況。
redis的作者也指出,在使用單節(jié)點(diǎn)redis鎖的時(shí)候,設(shè)置一個(gè)隨機(jī)種子作為key的值是很有必要的,保證了一個(gè)客戶(hù)端釋放的鎖必須是自己所持有的那個(gè)鎖。假設(shè)獲取鎖時(shí)set的不是一個(gè)隨機(jī)數(shù),而是一個(gè)固定值,
那么可能會(huì)出現(xiàn)下面的情況:
客戶(hù)端1獲取鎖成功
客戶(hù)端1在某個(gè)操作上阻塞了很長(zhǎng)時(shí)間
過(guò)期時(shí)間到了,鎖自動(dòng)釋放(但是在客戶(hù)端1看來(lái)自己還是持有鎖中)
客戶(hù)端2獲取到了對(duì)應(yīng)同一個(gè)資源的鎖
客戶(hù)端1從阻塞中恢復(fù)了,釋放掉自己持有的鎖,也就是釋放掉了客戶(hù)端2持有的鎖
客戶(hù)端2的鎖被客戶(hù)端1是否,失去安全性。
釋放鎖的操作,很多人直接用del命令,這會(huì)有很大的問(wèn)題,保證不了這個(gè)key是被加鎖人鎖刪。這時(shí)候需要用到隨機(jī)數(shù)了。
釋放鎖的操作有三步:
get 所持有鎖
判斷這個(gè)鎖是否自己所持有
刪除持有鎖
所以,這三步要保證原子性。用lua腳本來(lái)執(zhí)行,redis官方已經(jīng)提供腳本文件。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
這段腳本在執(zhí)行的時(shí)候,需要把前面的隨機(jī)數(shù)作為argv[1] 的值傳進(jìn)去,把cache_key作為keys[1]的值傳進(jìn)去。
public class RedisLockHelper {
@Resource
private R2mClusterClient r2mClusterClient;
/**
* 類(lèi)似于setNx的功能,同時(shí)設(shè)置過(guò)期時(shí)間為expire毫秒
*
* @param key 加鎖key
* @param value 確保在加鎖時(shí)間內(nèi)的唯一因子
* @param expire 過(guò)期時(shí)間的毫秒數(shù)
* @return
*/
private String setLock(String key, String value, long expire) {
return this.set(key, value, "NX", "PX", expire);
}
/**
* 刪除指定key value
* 如果 r2m中 key 對(duì)應(yīng)的value==value 返回 1
* 如果 r2m中 key 對(duì)應(yīng)的value!=value 返回 0
*
* @param key
* @return
*/
private boolean atomDelete(String key, String value) {
ListString> values = new ArrayList>();
values.add(value);
String sb = "if redis.call('get',KEYS[1])==ARGV[1] then " +
" return redis.call('del',KEYS[1]) " +
" else " +
" return 0" +
" end";
if (this.eval(sb, key, values) == 1) {
return true;
}
return false;
}
private Long eval(String mobel, String key, ListString> value) {
return (Long) this.r2mClusterClient.eval(mobel, key, value);
}
private String set(String key, String value, String nxxx, String expx, long time) {
return this.r2mClusterClient.set(key, value, nxxx, expx, time);
}
}
r2mClusterClient 就是jedis客戶(hù)端的封裝。
到此這篇關(guān)于php基于redis的分布式鎖實(shí)例詳解的文章就介紹到這了,更多相關(guān)php基于redis的分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- 詳解PHP解決守護(hù)進(jìn)程Redis假死
- thinkphp5redis緩存新增方法實(shí)例講解
- PHP使用Redis隊(duì)列執(zhí)行定時(shí)任務(wù)實(shí)例講解
- Thinkphp5+Redis實(shí)現(xiàn)商品秒殺代碼實(shí)例講解
- PHP操作Redis常用命令的實(shí)例詳解
- php在linux環(huán)境中如何使用redis詳解
- php操作redis命令及代碼實(shí)例大全
- php之redis短線(xiàn)重連案例講解