高併發下使用Redis悲觀鎖解決資料二次寫入

Lance發表於2020-11-17

悲觀鎖

在悲觀鎖的情況下,為了保證事務的隔離性,就須要一致性鎖定讀。
讀取資料時給加鎖,其他事務無法改動這些資料。
改動刪除資料時也要加鎖,其他事務無法讀取這些資料。

在做資料快取的時候,通常都是把資料從資料庫讀取出來,然後放入快取,接下來在快取的有效期內都是從快取讀取資料減少資料庫壓力。但是在高併發環境下,就有可能出現問題,比如根據指定格式從redis下拿資料,但是當下key是不存在的,那麼就需要往裡面寫資料,如果多個程式同時請求,會造成資料的二次寫入,如果邏輯不復雜還不會出現大的問題,問題是假如這個key的資料會變化呢?那麼這時候就需要加一個鎖機制了,就是獲取了鎖許可權的程式才有資格對資料操作。

(意思是,加入悲觀鎖,讓拿到鎖的程式,進行判斷key操作,如果有就讀取,如果沒有就寫入,讀取的時候別的程式無法改動這些資料,如果要是寫資料的時候別的事務不能讀取這條資料)

提到悲觀鎖,先通過網上給出的一個比較形象的比喻

拿健身房比喻,門口掛著把鑰匙(只有一把),想進去的人必須拿到這把鑰匙才行,拿到鑰匙的人可以進入,不管是熱身、喝水還是跑步都可以,直到他出來把鑰匙掛回牆上,下一個才能去爭取,拿到的才可以再進去。

聽著好像有點不人性化,所以悲觀鎖比較適合強一致性的場景,但效率比較低,特別是讀的併發低。樂觀鎖則適用於讀多寫少,併發衝突少的場景。

實現要點和思路

1、一個任務在同一時間段內只能被一個使用者所持有;
2、避免出現死任務,即避免任務被使用者長時間佔有,無法釋放。(使用redis的)

設定一個鎖的key,setnx是原子操作,只能一個程式寫入成功,寫入成功返回true(表示獲取鎖許可權),然後寫入內容立即釋放鎖即刪除鎖key。

如果只用SETNX命令設定鎖的話,如果當持有鎖的程式崩潰或刪除鎖失敗時,其他程式將無法獲取到鎖,問題就大了。

獲取不到鎖的程式去判斷鎖的剩餘有效時間,如果為-1,那麼表示沒有設定過期時間,則設定鎖的有效時間為5秒(預留5秒給拿到鎖的程式處理時間,足夠多了),返回true,等待鎖刪除。

<?php
$lock_key = 'LOCK_PREFIX' . $redis_key;
$is_lock = $redis->setnx($lock_key, 1); // 加鎖-》將上面變數當做key,判斷如果有key值,不做操作,如果沒有,將lockkey的值設定為1
if($is_lock == true){ // 獲取鎖許可權  
    $redis->setex($redis_key, $expire, $data); // 寫入內容
    // 釋放鎖
    $redis->del($lock_key);
}else{
    // 防止死鎖
    if($redis->ttl($lock_key) == -1){
        $redis->expire($lock_key, 5);
    }
    return true; // 獲取不到鎖許可權,直接返回
}

setnx :

只在鍵 key 不存在的情況下, 將鍵 key 的值設定為 value 。
若鍵 key 已經存在, 則 SETNX 命令不做任何動作。

setex:

將鍵 key 的值設定為 value , 並將鍵 key 的生存時間設定為 seconds 秒鐘。

如果鍵 key 已經存在, 那麼 SETEX 命令將覆蓋已有的值。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章