Redis 應用-分散式鎖

Gundy發表於2019-07-05

系列文章

當多個程式不在同一個系統中,就需要用分散式鎖控制多個程式對資源的訪問。

使用redis來實現分散式鎖主要用到以下命令:

  • SETNX KEY VALUE
    如果key不存在,就設定key對應字串value

  • expire KEY seconds
    設定key的過期時間

  • del KEY
    刪除key

程式碼實現如下:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$ok = $redis->setNX($key, $value);
if ($ok) {
    //獲取到鎖
    ... do something ...
    $redis->del($key);
}

上面程式碼有沒有問題呢?
如果我們在邏輯處理過程中出現了異常情況,導致KEY沒有刪除,那就出現了死鎖了。所以一般我們在拿到鎖之後再給KEY加一個過期時間

為了保證執行的原子性,使用了multi就有了如下程式碼

$redis->multi();
$redis->setNX($key, $value);
$redis->expire($key, $ttl);
$res = $redis->exec();
if($res[0]) {
    //獲取到鎖
    ... do something ...
    $redis->del($key);
}

但是這樣的又有一個問題第一個請求成功了,之後的請求雖然沒有拿到鎖但是每次都重新整理了鎖的時間。這樣我們設定鎖過期時間的意義就不存在了。所以我們在拿到鎖以後再進行過期時間的操作,這時候我們就可以祭出原子性操作的lua指令碼,程式碼如下

$script = <<<EOT
    local key   = KEYS[1]
    local value = KEYS[2]
    local ttl   = KEYS[3]

    local ok = redis.call('setnx', key, value)

    if ok == 1 then
    redis.call('expire', key, ttl)
    end
    return ok
EOT;

$res = $redis->eval($script, [$key,$val, $ttl], 3);
if($res) {
    //獲取到鎖
    ... do something ...
    $redis->del($key);
}

藉助lua指令碼雖然解決了問題,但是未免有些麻煩,Redis從 2.6.12 版本開始, SET 命令的行為可以通過一系列引數來修改:

  • EX second :設定鍵的過期時間為 second 秒。 SET key value EX second 效果等同於 SETEX key second value 。
  • PX millisecond :設定鍵的過期時間為 millisecond 毫秒。 SET key value PX millisecond 效果等同於 PSETEX key millisecond value 。
  • NX :只在鍵不存在時,才對鍵進行設定操作。 SET key value NX 效果等同於 SETNX key value 。
  • XX :只在鍵已經存在時,才對鍵進行設定操作。
$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));

if ($ok) {
    //獲取到鎖
    ... do something ...
    if ($redis->get($key) == $random) {
        $redis->del($key);
    }
}

可以看到上面我們我們的值引入了一個隨機數,這是為了防止邏輯處理時間過長導致鎖的過期時間已經失效,這時候下一個請求就獲得了鎖,但是前一個請求在邏輯處理完直接刪除了鎖。

鎖主要用在併發請求如秒殺等場景中,以上便是redis鎖的實現。

本文亦在微信公眾號【小道資訊】釋出,歡迎掃碼關注!
image

相關文章