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
Обратите внимание:
- Следующий паттерн не рекомендуется в пользу алгоритма Redlock, который обеспечивает лучшие гарантии и устойчивость к сбоям, хотя и немного сложнее в реализации.
- Мы всё же документируем старый паттерн, поскольку некоторые существующие реализации ссылаются на эту страницу как на ссылку. Кроме того, это интересный пример того, как команды Redis могут использоваться для создания программируемых примитивов.
- В любом случае, даже при условии примитива блокировки для единственного экземпляра, начиная с версии 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 вернёт
0C4. -
C4 отправляет
GET lock.fooдля проверки истечения срока действия блокировки. Если это не так, он будет ждать некоторое время и повторять попытку с начала. -
В противном случае, если блокировка истекла, потому что время Unix в
lock.fooменьше текущего времени Unix, C4 пытается выполнить:GETSET lock.foo <current Unix timestamp + lock timeout + 1>
-
Из-за семантики
GETSET, C4 может проверить, является ли старое значение, хранящееся вkey, всё ещё истекшей отметкой времени. Если да, блокировка была получена. -
Если другой клиент, например C5, был быстрее, чем C4, и получил блокировку с помощью операции
GETSET, операцияGETSETC4 вернёт неистекшую отметку времени. C4 просто начнёт с первого шага. Обратите внимание, что даже если C4 установит ключ немного в будущем, это не проблема.
Для повышения надёжности этого алгоритма блокировки клиент, удерживающий блокировку, всегда должен проверять, не истек ли тайм-аут, прежде чем разблокировать ключ с помощью DEL, так как сбои клиента могут быть сложными, не только из-за падения, но и из-за блокировки на длительное время некоторых операций и попытки выполнить DEL через много времени (когда блокировку уже держит другой клиент).
© 2006–2022 Salvatore Sanfilippo
Licensed under the Creative Commons Attribution-ShareAlike License 4.0.
https://redis.io/commands/setnx/