php結合redis高併發下,悲觀鎖解決資料二次寫入

OldBoy~發表於2018-02-06

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

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

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

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

實現要點和思路

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

設定一個鎖的key,setnx是原子操作,只能一個程式寫入成功,寫入成功返回true(表示獲取鎖許可權),然後寫入內容立即釋放鎖即刪除鎖key。如果只用SETNX命令設定鎖的話,如果當持有鎖的程式崩潰或刪除鎖失敗時,其他程式將無法獲取到鎖,問題就大了。獲取不到鎖的程式去判斷鎖的剩餘有效時間,如果為-1,那麼表示沒有設定過期時間,則設定鎖的有效時間為5秒(預留5秒給拿到鎖的程式處理時間,足夠多了),返回true,等待鎖刪除。

<?php
$lock_key = 'LOCK_PREFIX' . $redis_key;
$is_lock = $redis->setnx($lock_key, 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; // 獲取不到鎖許可權,直接返回
}

 

相關文章