系列文章
當(dāng)多個進(jìn)程不在同一個系統(tǒng)中,就需要用分布式鎖控制多個進(jìn)程對資源的訪問。
使用redis來實現(xiàn)分布式鎖主要用到以下命令:
SETNX KEY VALUE
如果key不存在氛改,就設(shè)置key對應(yīng)字符串valueexpire KEY seconds
設(shè)置key的過期時間del KEY
刪除key
代碼實現(xiàn)如下:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$ok = $redis->setNX($key, $value);
if ($ok) {
//獲取到鎖
... do something ...
$redis->del($key);
}
上面代碼有沒有問題呢篙悯?
如果我們在邏輯處理過程中出現(xiàn)了異常情況,導(dǎo)致KEY沒有刪除缴允,那就出現(xiàn)了死鎖了。所以一般我們在拿到鎖之后再給KEY加一個過期時間
為了保證執(zhí)行的原子性,使用了multi
就有了如下代碼
$redis->multi();
$redis->setNX($key, $value);
$redis->expire($key, $ttl);
$res = $redis->exec();
if($res[0]) {
//獲取到鎖
... do something ...
$redis->del($key);
}
但是這樣的又有一個問題第一個請求成功了乎赴,之后的請求雖然沒有拿到鎖但是每次都刷新了鎖的時間。這樣我們設(shè)置鎖過期時間的意義就不存在了潮尝。所以我們在拿到鎖以后再進(jìn)行過期時間的操作榕吼,這時候我們就可以祭出原子性操作的lua腳本,代碼如下
$script = <<<EOT
local key = KEYS[1]
local value = KEYS[2]
local ttl = KEYS[3]
local ok = redis.call('setnx', key, value)
if ok == 1 then
redis.call('expire', key, ttl)
end
return ok
EOT;
$res = $redis->eval($script, [$key,$val, $ttl], 3);
if($res) {
//獲取到鎖
... do something ...
$redis->del($key);
}
借助lua腳本雖然解決了問題勉失,但是未免有些麻煩羹蚣,Redis從 2.6.12 版本開始, SET 命令的行為可以通過一系列參數(shù)來修改:
- EX second :設(shè)置鍵的過期時間為 second 秒乱凿。 SET key value EX second 效果等同于 SETEX key second value 顽素。
- PX millisecond :設(shè)置鍵的過期時間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 徒蟆。
- NX :只在鍵不存在時胁出,才對鍵進(jìn)行設(shè)置操作。 SET key value NX 效果等同于 SETNX key value 段审。
- XX :只在鍵已經(jīng)存在時全蝶,才對鍵進(jìn)行設(shè)置操作。
$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));
if ($ok) {
//獲取到鎖
... do something ...
if ($redis->get($key) == $random) {
$redis->del($key);
}
}
可以看到上面我們我們的值引入了一個隨機(jī)數(shù)寺枉,這是為了防止邏輯處理時間過長導(dǎo)致鎖的過期時間已經(jīng)失效抑淫,這時候下一個請求就獲得了鎖,但是前一個請求在邏輯處理完直接刪除了鎖姥闪。
鎖主要用在并發(fā)請求如秒殺等場景中始苇,以上便是redis鎖的實現(xiàn)。
本文亦在微信公眾號【小道資訊】發(fā)布筐喳,歡迎掃碼關(guān)注催式!
image