redis setnx 過期時間_Redis加鎖的幾種實現


redis setnx 過期時間_Redis加鎖的幾種實現

 

844aed171dfec7ca1b6f4d527b964ff1.png

我們經常在工作中會碰到一些重復請求、並發等問題,而給資源加鎖是一種不錯的手段。我們今天就整理下使用redis作為分布式鎖的幾種實現。

redis可以用於幾個命令是:INCRSETNXSET

1. 使用INCR加鎖

這種加鎖的思路是, key 不存在,那么 key 的值會先被初始化為 0 ,然后再執行 INCR 操作進行加一。然后其它用戶在執行 INCR 操作進行加一時,如果返回的數大於 1 ,說明這個鎖正在被使用當中。

/***
  1、 客戶端A請求服務器獲取key的值為1表示獲取了鎖
  2、 客戶端B也去請求服務器獲取key的值為2表示獲取鎖失敗
  3、 客戶端A執行代碼完成,刪除鎖
  4、 客戶端B在等待一段時間后在去請求的時候獲取key的值為1表示獲取鎖成功
  5、 客戶端B執行代碼完成,刪除鎖
**/
$res = $redis->incr($key);  // 自增1
$redis->expire($key, $ttl); // 設置鎖的有效期
if($res == 1){  
    // 獲取資源成功
}else{
    // 資源被其他請求占用
}

 

  1.  
     

2. 使用SETNX加鎖

這種加鎖的思路是,如果 key 不存在,將 key 設置為 value,如果 key 已存在,則 SETNX 不做任何動作。

/***
 1、 客戶端A請求服務器設置key的值,如果設置成功就表示加鎖成功
 2、 客戶端B也去請求服務器設置key的值,如果返回失敗,那么就代表加鎖失敗
 3、 客戶端A執行代碼完成,刪除鎖
 4、 客戶端B在等待一段時間后在去請求設置key的值,設置成功
 5、 客戶端B執行代碼完成,刪除鎖
**/
$res = $redis->setNX($key, $value); // 當key不存在時設置key=value
$redis->expire($key, $ttl); // 設置鎖的有效期
if($res){  
    // 獲取資源成功
}else{
    // 資源被其他請求占用
}

 

  1.  
    /

上面兩種方法都有一個問題,會發現,都需要設置 key 過期時間。那么為什么要設置key過期時間呢?如果請求執行因為某些原因意外退出了,導致創建了鎖但是沒有刪除鎖,那么這個鎖將一直存在(redis不設置key的過期時間,默認是永久的),以至於一直處於加鎖狀態。於是乎我們需要給鎖加一個過期時間以防不測。

但是借助 Expire 來設置就不是原子性操作了。所以還可以通過redis事務來確保原子性。那上面的代碼就要優化成:

// 第一種方式的加鎖
$redis->multi();    // 標記一個事務塊的開始
$res = $redis->incr($key);  
$redis->expire($key, $ttl);
$redis->exec();   // 提交事務
if($res == 1){  
    // 獲取資源成功
}else{
    // 資源被其他請求占用
}
 
// 第二種方式的加鎖
$redis->multi();    // 標記一個事務塊的開始
$res = $redis->setNX($key, $value); 
$redis->expire($key, $ttl); 
$redis->exec();   // 提交事務
if($res){  
    // 獲取資源成功
}else{
    // 資源被其他請求占用
}

 

  1.  

上面代碼看起來是不是很繁瑣。好在redis官方從版本 2.6.12 開始 SET 命令本身已經包含了設置過期時間的功能。

3. 使用SET加鎖

/*** 
 1、 客戶端A請求服務器設置key的值,如果設置成功就表示加鎖成功
 2、 客戶端B也去請求服務器設置key的值,如果返回失敗,那么就代表加鎖失敗
 3、 客戶端A執行代碼完成,刪除鎖
 4、 客戶端B在等待一段時間后在去請求設置key的值,設置成功
 5、 客戶端B執行代碼完成,刪除鎖
**/    
 
$res = $redis->set($key, $value, ['nx', 'ex' => $ttl]);  //nx代表當key不存在時設置 ex代表設置過期時間
if($res){  
    // 獲取資源成功
}else{
    // 資源被其他請求占用
}

 

  1.  

4. 其他問題

雖然上面一步已經滿足了我們的需求,但是還是要考慮其它問題? - 1、 redis發現鎖失敗了要怎么辦?中斷請求還是循環請求? - 2、 循環請求的話,如果有一個獲取了鎖,其它的在去獲取鎖的時候,是不是容易發生搶鎖的可能? - 3、 鎖提前過期后,客戶端A還沒執行完,然后客戶端B獲取到了鎖,這時候客戶端A執行完了,會不會在刪鎖的時候把B的鎖給刪掉?

5. 解決辦法

  • 針對問題1:使用循環請求,循環請求去獲取鎖
  • 針對問題2:針對第二個問題,在循環請求獲取鎖的時候,加入睡眠功能,等待幾毫秒在執行循環
  • 針對問題3:在加鎖的時候存入的key是隨機的。這樣的話,每次在刪除key的時候判斷下存入的key里的value和自己存的是否一樣
do {  //針對問題1,使用循環
    $timeout = 10;
    $roomid = 10001;
    $key = 'room_lock';
    $value = 'room_'.$roomid;  //分配一個隨機的值針對問題3
    $isLock = $redis->set($key, $value, 'ex', $timeout, 'nx');//ex 秒
    if ($isLock) {
        if ($redis->get($key) == $value) {  //防止提前過期,誤刪其它請求創建的鎖
            //執行內部代碼
            $redos->del($key);
            continue;//執行成功刪除key並跳出循環
        }
    } else {
        usleep(5000); //睡眠,降低搶鎖頻率,緩解redis壓力,針對問題2
    }
} while(!$isLock);

 

 

就這么多了,喜歡就點個贊吧~

參考:

  • http://ukagaka.github.io/php/2017/09/21/redisLock.html
作者博客鏈接​metmoon.com
 


免責聲明!

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



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