Redis鎖的基本應用


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

常見的方案之一:setnx,其他線程必須拿到這個值,才能繼續往下執行,否則等待。該命令是原子操作,所以可以防止並發情況的發生。

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 設置的過期時間,那么這個鎖就自動消失了,仍會有其他進程並發執行業務代碼,且不同進程的鎖相互覆蓋。所以這個方案也不能有效防止並發。

解決方法: 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 的值已被其他進程修改
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM