Как использовать Redis для реализации распределенных блокировок

Java Redis

Блокировки — это тема, которую мы не можем обойти при проектировании и реализации большинства систем. При возникновении состояния гонки могут возникнуть непредсказуемые проблемы без защищенной операции.

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

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

  • Реализовано с помощью базы данных.
  • Реализовано с использованием системы кэширования, такой как Redis.
  • Реализовано с использованием распределенной системы координации, такой как Zookeeper.

Среди них Redis является простым и гибким, высокодоступным и распределенным, а также поддерживает постоянство. В этой статье представлена ​​реализация распределенных блокировок на основе Redis.

Семантика SETNX

Основным принципом использования Redis для реализации распределенных блокировок является инструкция SETNX. Его семантика такова:

SETNX key value

При выполнении команды, еслиkeyне существует, установитеkeyзначениеvalue(такой жеset);еслиkeyуже существует, операция присваивания не выполняется. и используйте другой идентификатор возвращаемого значения.документ описания команды

Также доступно через опцию NX команды SET:

SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

NX — выполнить операцию назначения только в том случае, если ключ не существует.документ описания командыКак описано ниже, используя параметр NX команды SET, вы можете одновременно использовать другие параметры, такие как EX/PX, для установки времени ожидания, что является лучшим способом.

SETNX реализует распределенные блокировки

Ниже мы сравним несколько конкретных реализаций.

Сценарий 1: SETNX + удаление

Псевдокод выглядит следующим образом:

setnx lock_a random_value
// do sth
delete lock_a

Проблема с этой реализацией заключается в том, что как только служба получает блокировку и по какой-то причине зависает, блокировка не может быть снята автоматически. в результате тупик.

Сценарий 2: SETNX + SETEX

Псевдокод выглядит следующим образом:

setnx lock_a random_value
setex lock_a 10 random_value // 10s超时
// do sth
delete lock_a

Установите период ожидания по мере необходимости. Это решение решает проблему взаимоблокировки решения 1, но в то же время вводит новую проблему взаимоблокировки: Если служба зависнет после setnx и до setex, она попадет в тупик. Основная причина в том, что setnx/setex разделен на два шага, неатомарные операции.

Сценарий 3: НАСТРОЙКА NX PX

Псевдокод выглядит следующим образом:

SET lock_a random_value NX PX 10000 // 10s超时
// do sth
delete lock_a

Это решение объединяет два шага блокировки и установки времени ожидания в одну атомарную операцию с помощью опции set NX/PX, чтобы решить проблемы решений 1 и 2. (Семантика опций PX и EX одинаковая, разница только в единицах.) Эта схема в настоящее время поддерживается большинством схем развертывания SDK и Redis, поэтому рекомендуется именно ее. Но это решение также имеет следующие проблемы:

Если блокировка снята по ошибке (например, по тайм-ауту), или вытеснена по ошибке, или блокировка потеряна из-за проблем с Redis и т. д., это не может быть быстро распознано.

Вариант 4: УСТАНОВИТЬ случайное значение ключа NX PX

На основании 3 схема 4 увеличивает проверку на значение, и только снимает блокировку, добавленную ею самой. Аналогично CAS, но с возможностью сравнения и удаления. Эта схема не поддерживается нативными командами redis, для обеспечения атомарности ее нужно реализовывать через lua-скрипты:.

Псевдокод выглядит следующим образом:

SET lock_a random_value NX PX 10000
// do sth
eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock_a random_value

Эта схема более строгая: даже если блокировка ошибочно снята из-за какого-то исключения, она может частично гарантировать правильное снятие блокировки. И когда блокировка снимается, он может определить, была ли блокировка ошибочно вытеснена или снята по ошибке, чтобы выполнить специальную обработку.

Меры предосторожности

сверхурочное время

Как видно из приведенного выше описания, период тайм-аута является важной переменной:

Тайм-аут не может быть слишком коротким, иначе блокировка будет автоматически снята до завершения задачи, в результате чего ресурс окажется за пределами защиты блокировки. Тайм-аут не может быть слишком большим, иначе это приведет к длительному ожиданию после неожиданной взаимоблокировки. Если не обрабатывается доступом человека. Поэтому рекомендуется разумно измерять период ожидания в соответствии с содержанием задачи и устанавливать период ожидания в несколько раз больше содержания задачи. Если это действительно неясно и требования более строгие, это может быть реализовано с периодическими тайм-аутами обновления setex/expire.

Повторить попытку

Если блокировка не может быть получена, рекомендуется провести опрос и подождать в соответствии с характером задачи и бизнес-формой. Время ожидания должно относиться ко времени выполнения задачи.

Сравнение с транзакциями Redis

setnx использует более гибкую схему. Транзакционная реализация multi/exec более сложна. А некоторые кластерные решения Redis (такие как codis) не поддерживают транзакции multi/exec.

Подписывайтесь на нас

关注我们