Приложение Redis — распределенная блокировка

Redis

серия статей

Когда несколько процессов не находятся в одной системе, необходимо использовать распределенные блокировки для управления доступом нескольких процессов к ресурсам.

Использование Redis для реализации распределенных блокировок в основном использует следующие команды:

  • SETNX KEY VALUEЕсли ключ не существует, установите ключ, соответствующий строковому значению.

  • expire KEY secondsУстановите срок действия ключа

  • del KEYудалить ключ

Код реализован следующим образом:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$ok = $redis->setNX($key, $value);
if ($ok) {
    //获取到锁
    ... do something ...
    $redis->del($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-скрипта, это немного хлопотно.Начиная с версии 2.6.12 Redis, поведение команды SET можно изменить с помощью ряда параметров:

  • EX second : Установите время действия ключа в секундах. Значение ключа SET EX в секундах имеет тот же эффект, что и второе значение ключа SETEX.
  • PX миллисекунды: установите время истечения ключа в миллисекунды миллисекунды. Значение ключа SET PX миллисекунды имеет тот же эффект, что и значение ключа PSETEX в миллисекундах.
  • NX : Установите ключ, только если ключ не существует. Значение ключа SET NX имеет тот же эффект, что и значение ключа SETNX.
  • XX : Установите ключ, только если ключ уже существует.
$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));

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

Видно, что наше значение ввело случайное число выше. Это сделано для того, чтобы время истечения срока действия блокировки не было недействительным из-за длительного времени логической обработки. В это время следующий запрос получит блокировку, но предыдущий запрос будет обработан сразу после обработки логики.Снята блокировка.

Блокировки в основном используются в параллельных запросах, таких как всплески и др. Выше приведена реализация блокировок Redis.

Эта статья также опубликована в общедоступной учетной записи WeChat [Trail News]. Пожалуйста, отсканируйте код, чтобы следовать!