Redis鎖的基本應用

處理高併發問題時,咱們常常用 Redis 進行加鎖操做,目的是爲了解決併發可能帶來的問題。作一個簡單的總結redis

常見的方案之一:setnx,其餘線程必須拿到這個值,才能繼續往下執行,不然等待。該命令是原子操做,因此能夠防止併發狀況的發生。session

while(!$redis->setnx('lock', '1')) {    // 設置鎖
    usleep(100000);
}

// 執行業務代碼

$redis->del('lock');    // 釋放鎖

可是該方案有個弊端,若是設置鎖後進程崩潰,那麼該鎖永遠不會釋放。通常解決方法是在 setnx 的時候設置過時時間,則能夠解決線程奔潰鎖沒法釋放的問題。但若是設置鎖和設置鎖的過時時間不是原子操做,仍然不能防止併發的狀況發生。好在 Redis 中的 set 命令提供了這些參數使用,NX 就和 setnx 同樣, EX 能夠在 set 的時候加上過時時間。併發

$expire = 5;    // 鎖的過時時間
while(!$redis->set('lock', '1', ['NX', 'EX'=>$expire])) {
    usleep(100000);
}

// 執行業務代碼

$redis->del('lock');

可是在這種狀況下,當一個進程執行出現問題或執行時間超過 setnx 設置的過時時間,那麼這個鎖就自動消失了,仍會有其餘進程併發執行業務代碼,且不一樣進程的鎖相互覆蓋。因此這個方案也不能有效防止併發。dom

解決方法: watch,在 set 的時候設置 NX 與 EX,而且設置值爲隨機數(惟一),當 A 進程設置鎖後,後續進程都沒法設置鎖。A 進程業務邏輯完成後,對比隨機數是否一致,若是一致再刪除,若是在刪除過程當中,發現 key 的值被修改,則刪除失敗。防止 A 進程超時後,鎖被後續進程獲取,這個時候若是 A 進程刪除鎖,就會把後面的鎖給刪了。高併發

$expire = 5;
$random = session_create_id();  
while(!$redis->set('lock', $random, ['NX', 'EX'=>$expire])) {
    usleep(100000);
}
$redis->watch("lock");  // 監聽 lock 的值

// 執行業務代碼

$ret = $redis->multi(Redis::MULTI)
    ->del("lock")
    ->exec();   // multi 的做用在於若被 watch 的鍵被修改、刪除、覆蓋,那麼 exec 就會執行失敗
if($ret) {
    $redis->del("lock"); // 釋放鎖
} else {
    // 表明 lock 的值已被其餘進程修改
}
相關文章
相關標籤/搜索