在做數據緩存的時候,通常都是把數據從數據庫讀取出來,然后放入緩存,接下來在緩存的有效期內都是從緩存讀取數據減少數據庫壓力。但是在高並發環境下,就有可能出現問題,比如根據指定格式從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; // 獲取不到鎖權限,直接返回
}
