Spec-Zone.ru › Redis

SETNX

SETNX (deprecated)

Начиная с версии Redis 2.6.12, эта команда считается устаревшей.

Её можно заменить на SET с аргументом NX при миграции или написании нового кода.

Синтаксис
SETNX key value
Доступна с версии:
1.0.0
Сложность по времени:
O(1)
Категории ACL:
@write, @string, @fast,

Установить key для хранения строки value, если key не существует. В этом случае она эквивалентна SET. Если key уже содержит значение, никакая операция не выполняется. SETNX — это сокращение от «SET, если Nе Xуществует».

Возврат

Целочисленный ответ, конкретно:

  • 1 если ключ был установлен
  • 0 если ключ не был установлен

Примеры

SETNX mykey "Hello"
SETNX mykey "World"
GET mykey

Паттерн проектирования: Блокировка с помощью SETNX

Обратите внимание:

  1. Следующий паттерн не рекомендуется в пользу алгоритма Redlock, который обеспечивает лучшие гарантии и устойчивость к сбоям, хотя и немного сложнее в реализации.
  2. Мы всё же документируем старый паттерн, поскольку некоторые существующие реализации ссылаются на эту страницу как на ссылку. Кроме того, это интересный пример того, как команды Redis могут использоваться для создания программируемых примитивов.
  3. В любом случае, даже при условии примитива блокировки для единственного экземпляра, начиная с версии 2.6.12, можно создать гораздо более простой примитив блокировки, эквивалентный описанному здесь, используя команду SET для получения блокировки и простой скрипт Lua для освобождения блокировки. Паттерн документирован на странице команды SET.

При этом SETNX может и исторически использовался в качестве примитива блокировки. Например, для получения блокировки ключа foo, клиент мог бы попробовать следующее:

SETNX lock.foo <current Unix time + lock timeout + 1>

Если SETNX возвращает 1, клиент получил блокировку, установив ключ lock.foo на время Unix, по истечении которого блокировка больше не будет считаться действительной. Позже клиент будет использовать DEL lock.foo для освобождения блокировки.

Если SETNX возвращает 0, ключ уже заблокирован каким-то другим клиентом. Мы можем либо вернуть значение вызывающему объекту, если это неблокирующая блокировка, либо войти в цикл, пытаясь удерживать блокировку до тех пор, пока она не будет успешно получена или не истечёт какой-либо тайм-аут.

Обработка тупиков

В алгоритме блокировки выше есть проблема: что произойдёт, если клиент завершит работу, упадет или по каким-либо другим причинам не сможет освободить блокировку? Можно обнаружить это состояние, поскольку ключ блокировки содержит отметку времени Unix. Если такая отметка времени равна текущему времени Unix, блокировка больше не действительна.

В этом случае мы не можем просто вызвать DEL для удаления ключа блокировки и затем попытаться выполнить SETNX, так как здесь есть проблема гонки, когда несколько клиентов обнаруживают истекшую блокировку и пытаются её освободить.

  • C1 и C2 читают lock.foo для проверки отметки времени, потому что оба получили 0 после выполнения SETNX, поскольку блокировка всё ещё удерживается C3, который упал после удержания блокировки.
  • C1 отправляет DEL lock.foo
  • C1 отправляет SETNX lock.foo и это успешно.
  • C2 отправляет DEL lock.foo
  • C2 отправляет SETNX lock.foo и это успешно.
  • ОШИБКА: C1 и C2 получили блокировку из-за проблемы гонки.

К счастью, эту проблему можно избежать, используя следующий алгоритм. Давайте посмотрим, как C4, наш разумный клиент, использует правильный алгоритм:

  • C4 отправляет SETNX lock.foo для получения блокировки

  • Упавший клиент C3 всё ещё удерживает её, поэтому Redis вернёт 0 C4.

  • C4 отправляет GET lock.foo для проверки истечения срока действия блокировки. Если это не так, он будет ждать некоторое время и повторять попытку с начала.

  • В противном случае, если блокировка истекла, потому что время Unix в lock.foo меньше текущего времени Unix, C4 пытается выполнить:

    GETSET lock.foo <current Unix timestamp + lock timeout + 1>
    
  • Из-за семантики GETSET, C4 может проверить, является ли старое значение, хранящееся в key, всё ещё истекшей отметкой времени. Если да, блокировка была получена.

  • Если другой клиент, например C5, был быстрее, чем C4, и получил блокировку с помощью операции GETSET, операция GETSET C4 вернёт неистекшую отметку времени. C4 просто начнёт с первого шага. Обратите внимание, что даже если C4 установит ключ немного в будущем, это не проблема.

Для повышения надёжности этого алгоритма блокировки клиент, удерживающий блокировку, всегда должен проверять, не истек ли тайм-аут, прежде чем разблокировать ключ с помощью DEL, так как сбои клиента могут быть сложными, не только из-за падения, но и из-за блокировки на длительное время некоторых операций и попытки выполнить DEL через много времени (когда блокировку уже держит другой клиент).

© 2006–2022 Salvatore Sanfilippo
Licensed under the Creative Commons Attribution-ShareAlike License 4.0.
https://redis.io/commands/setnx/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API