Анализ решения распределенной блокировки Redis
1. Предпосылки
Когда мы каждый день совершаем покупки на веб-сайтах электронной коммерции, мы часто сталкиваемся с некоторыми сценариями с высокой степенью параллелизма, такими как действия seckill, которые часто появляются в приложениях электронной коммерции, ограниченные покупки купонов, наша система захвата билетов на поезд Qunar.com и т. д. сценарии Общей чертой является всплеск доступа.Хотя конструкция системы будет оптимизирована за счет ограничения тока, асинхронности, очередей и т. д., общий параллелизм в несколько раз больше, чем обычно.Чтобы избежать проблем с параллелизмом и предотвратить перепроданность запасов, предусмотрите пользователям с Для удобства покупок в этих системах будет использоваться механизм блокировки.
Для параллельных сценариев с одним процессом вы можете использовать блокировки, предоставляемые языками программирования и соответствующими библиотеками классов, например синхронизированный синтаксис в Java и класс ReentrantLock, чтобы избежать проблем параллелизма.
Если в распределенном сценарии для достижения синхронного доступа к коду и ресурсам потоков разных клиентов и для обеспечения безопасности обработки общих данных в нескольких потоках требуется технология распределенной блокировки.
Так что же такое распределенная блокировка? Распределенная блокировка — это реализация блокировки, которая контролирует общий доступ к общим ресурсам между распределенными системами или разными системами.Если разные системы или разные хосты одной системы совместно используют определенный ресурс, часто требуется взаимное исключение для предотвращения друг друга.Вмешательство гарантирует согласованность.
Относительно безопасный распределенный замок обычно должен иметь следующие характеристики:
- взаимная исключительность. Взаимное исключение — основная функция блокировок, а блокировки могут удерживаться только одним потоком за раз для выполнения операций критической секции.
- Тайм-аут выпуска. С помощью тайм-аута можно избежать взаимоблокировки, предотвратить ненужное ожидание потока и потерю ресурсов, подобно настройке параметра innodblockwait_timeout в движке MySQL InnoDB.
- повторный вход. Поток, который удерживает блокировку, может снова запросить блокировку, предотвращая снятие блокировки до того, как поток завершит свою операцию критической секции.
- Высокая производительность и высокая доступность. Накладные расходы производительности процесса на блокировку и снятие блокировок должны быть как можно ниже, и в то же время должна быть обеспечена высокая доступность для предотвращения случайного выхода из строя распределенных блокировок.
Видно, что реализация распределенных блокировок заключается не только в блокировке ресурсов, но также должна соответствовать некоторым дополнительным функциям, чтобы избежать взаимоблокировок, отказов блокировок и других проблем.
2 Реализация распределенных замков
В настоящее время существует множество способов реализации распределенных блокировок, и наиболее распространенными из них являются:
- Распределенная блокировка Memcached
Воспользуйтесь преимуществами команды добавления Memcached. Эта команда является атомарной операцией, и добавление может быть успешным только в том случае, если ключ не существует, что означает, что поток получает блокировку.
- Распределенный замок Zookeeper
Используйте последовательные временные узлы Zookeeper для реализации распределенных блокировок и очередей ожидания. Как фреймворк, предоставляющий решения для распределенных приложений, ZooKeeper предоставляет несколько очень хороших функций, таких как автоматическое удаление znodes эфемерного типа, а ZooKeeper также предоставляет механизм наблюдения, который позволяет использовать распределенные блокировки на стороне клиента. блокировка: если блокировка не удалась, она блокируется до тех пор, пока блокировка не будет получена.
- Chubby
Служба распределенных блокировок общего назначения, реализованная Google, чем-то похожа на ZooKeeper, но имеет много отличий. Chubby решает проблему отказа блокировки, вызванного задержкой запроса, с помощью механизма секвенсора.
- Распределенная блокировка Redis
Распределенная блокировка, основанная на автономной реализации Redis, аналогична реализации Memcached. Используется команда Redis SETNX. Эта команда также является атомарной операцией. Установка может быть успешной только в том случае, если ключ не существует. Распределенная блокировка Redlock на основе многомашинной реализации Redis — это более безопасный и эффективный механизм реализации, предложенный автором Redis, antirez, с целью стандартизации реализации распределенных блокировок Redis.
В этой статье в основном обсуждаются и анализируются несколько реализаций и существующие проблемы распределенных блокировок на основе Redis.
3 Распределенная блокировка Redis
Используя Redis в качестве распределенной блокировки, по сути, цель, которую нужно достичь, состоит в том, чтобы процесс занял единственную «яму» в Redis.Когда другие процессы также хотят занять яму, они обнаруживают, что там уже кто-то сидит, поэтому они отказаться или подождать и повторить попытку позже.
В настоящее время существует два основных типа распределенных блокировок на основе Redis.Один основан на одной машине, а другой основан на нескольких машинах Redis.Независимо от того, какой метод реализации используется, необходимо реализовать три распределенных блокировки: блокировка, разблокировка и тайм-аут блокировки.Основной элемент блокировки.
3.1 Распределенная блокировка на основе автономной реализации Redis
3.1.1 Использование инструкции SETNX
Самый простой способ блокировки — напрямую использовать команду SETNX Redis.Эта команда устанавливает значение ключа только в том случае, если ключ не существует.Если ключ уже существует, команда SETNX ничего не делает. Ключ — это уникальный идентификатор замка, которому можно присвоить имя в соответствии с ресурсами, которые бизнесу необходимо заблокировать.
Например, чтобы заблокировать определенный продукт при быстрой распродаже в торговом центре, ключ может быть установлен на lock_resource_id, а значение может быть установлено на любое значение.После использования ресурса используйте клавишу DEL, чтобы удалить ключ, чтобы снять блокировку. Весь процесс выглядит следующим образом:
SETNX lock_resource_id lock_value
do something
DEL lock_resource_id
Очевидно, что этот способ получения блокировок очень прост, но есть и проблема, то есть проблема тайм-аута блокировки, один из трех основных элементов распределенных блокировок, упомянутых выше, то есть, если процесс получения блокировки находится в процесс обработки бизнес-логики При возникновении исключения инструкция DEL может не выполниться, блокировка не может быть снята, и ресурс будет заблокирован навсегда.
Таким образом, после использования SETNX для получения блокировки вы должны установить время истечения срока действия ключа, чтобы гарантировать, что даже если он не будет освобожден явным образом, он будет автоматически освобожден после получения блокировки в течение определенного периода времени, чтобы предотвратить доступ к ресурсу. от монополизации в течение длительного времени. Поскольку SETNX не поддерживает установку времени истечения срока действия, требуется дополнительная команда EXPIRE, весь процесс выглядит следующим образом:
SETNX lock_resource_id lock_value
EXPIRE lock_resource_id 10
do something
DEL lock_resource_id
Распределенная блокировка, реализованная таким образом, все еще имеет серьезную проблему.Поскольку две операции SETNX и EXPIRE не являются атомарными, если между выполнением SETNX и EXPIRE возникает исключение, SENX выполняется успешно, но EXPIRE не выполняется, в результате в этом блокировка становится «бессмертной».В этом случае может возникнуть проблема тайм-аута блокировки, упомянутая выше, и другие процессы не могут нормально получить блокировку.
3.1.2 Использование расширенной инструкции SET
Чтобы решить проблему неатомарности двух операций SETNX и EXPIRE, можно использовать расширенные параметры инструкции SET Redis, чтобы две операции SETNX и EXPIRE могли выполняться атомарно.Весь процесс следующим образом:
SET lock_resource_id lock_value NX EX 10
do something
DEL lock_resource_id
В этой инструкции SET:
- NX означает, что SET может быть выполнен только тогда, когда значение ключа, соответствующее lock_resource_id, не существует.
успех. Гарантируется, что только первый запрашивающий клиент может получить блокировку, и никакие другие клиенты не могут получить блокировку, пока блокировка не будет снята. - EX 10 означает, что блокировка автоматически истечет через 10 секунд, и бизнес может установить размер этого времени в соответствии с реальной ситуацией.
Но этот метод все еще не может полностью решить проблему тайм-аута распределенной блокировки:
-
блокировка снимается раньше. Если логическое выполнение между потоком A слишком длинный (или заблокирован во время выполнения нити A), он выпускается после превышения срока действия блокировки, но логика потока A в критической области еще не завершена Тогда поток B на этот раз
Блокировку можно повторно получить заранее, так что код критической секции не может быть строго сериализован. - Блокировка удалена по ошибке. Если поток A в приведенной выше ситуации выполняется, он не знает, что держателем блокировки в это время является поток B, поток A продолжит выполнение инструкции DEL для снятия блокировки, если логика потока B в критической секции не был выполнен, поток A фактически освобождает блокировку потока B.
Во избежание вышеуказанной ситуации рекомендуется не использовать распределенные блокировки Redis в сценариях, где время выполнения слишком велико.В то же время более безопасным подходом является оценка блокировки перед выполнением DEL для снятия блокировки и проверка является ли текущий держатель замка им самим.
Конкретная реализация состоит в том, чтобы установить значение в уникальное случайное число (или идентификатор потока) при блокировке и сначала определить, является ли случайное число последовательным при освобождении блокировки, а затем выполнить операцию освобождения, чтобы гарантировать, что блокировки удерживаются другими потоками. не будет снята по ошибке. , если только срок действия блокировки не истек и сервер не снимает ее автоматически, весь процесс выглядит следующим образом:
set lock_resource_id random_value nx ex 10
do something
if random_value==lock_resource_id.value
del lock_resource_id
Но оценка значения и удаление ключа — это две независимые операции, а не атомарные, поэтому здесь необходимо использовать сценарий Lua для обработки, поскольку сценарий Lua может обеспечить атомарное выполнение нескольких последовательных инструкций.
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
Распределенная блокировка на основе одного узла Redis в основном завершена, но это не идеальное решение, а относительно законченное решение, потому что оно не решает полностью проблему того, что другие потоки используют в своих интересах тот факт, что текущий тайм-аут выполнения потока блокировка снимается заранее.
3 Распределенные блокировки с использованием Redisson
Как решить проблему, что блокировка снимается раньше?
Вы можете использовать функцию повторного входа в блокировку, чтобы поток, который получает блокировку, запускал поток демона таймера и выполнял его каждые expireTime/3, чтобы проверить, существует ли блокировка потока.Если она существует, сбросьте время истечения срока действия блокировки на expireTime. , то есть поток демона используется для «обновления» блокировки, чтобы предотвратить досрочное освобождение блокировки из-за истечения срока действия.
Конечно, логика реализации этого демона для бизнеса все еще относительно сложна, и могут возникнуть некоторые неизвестные проблемы.
В настоящее время Redisson, широко используемая инфраструктура с открытым исходным кодом, используемая интернет-компаниями в производственной среде, очень хорошо решает эту проблему.Он очень прост в использовании и поддерживает несколько архитектур развертывания, таких как Redis с одним экземпляром, Redis MS, Redis Sentinel и Кластер Редис.
Заинтересованные друзья могут проверить официальную документацию или исходный код:
Принцип реализации показан на рисунке (на рисунке в качестве примера используется кластер redis):
Распределенная блокировка Redlock на основе многомашинной реализации Redis
Вышеупомянутые распределенные блокировки, основанные на автономной реализации Redis, на самом деле имеют проблему, то есть блокировка действует только на один узел Redis, даже если Redis обеспечивает высокую доступность через Sentinel, но поскольку репликация Redis является асинхронной, главный узел получает после блокировка достигнута, аварийное переключение происходит без завершения синхронизации данных.В это время потоки на других клиентах все еще могут получить блокировку, поэтому безопасность блокировки будет потеряна.
Весь процесс выглядит следующим образом:
- Клиент A получает блокировку от главного узла.
- Сбой главного узла Во время процесса репликации ведущий-ведомый ключ, соответствующий блокировке, не синхронизируется с подчиненным узлом.
- Подчиненный узел обновлен до главного узла, но в этот момент на ведущем узле нет данных блокировки.
- Клиент B запрашивает новый главный узел и получает блокировку, соответствующую тому же ресурсу.
- Когда несколько клиентов одновременно удерживают блокировки на одном и том же ресурсе, взаимное исключение блокировок не выполняется.
Из-за этого в распределенной среде Redis автор Redis, antirez, предоставляет алгоритм RedLock для реализации распределенной блокировки.Алгоритм, вероятно, такой:
Если предположить, что узлов Redis N (N>=5), эти узлы полностью независимы друг от друга, репликация master-slave или другие механизмы координации кластера отсутствуют. Убедитесь, что N узлов получены и освобождены с использованием одного и того же метода. как в случае единственного экземпляра Redis.
В процессе получения блокировки клиент должен выполнить следующие операции:
-
Получить текущее время Unix в миллисекундах.
-
Попытки получить блокировки от 5 экземпляров, используя один и тот же ключ и уникальное значение (например, UUID) в последовательности. При запросе блокировки от Redis клиент должен установить сетевое соединение и время ожидания ответа, которое должно быть меньше времени истечения срока действия блокировки. Например, если время автоматического истечения срока действия блокировки составляет 10 секунд, время ожидания должно составлять от 5 до 50 миллисекунд. Это может помешать клиенту ждать ответа, когда Redis на стороне сервера завис. Если сервер не отвечает в течение указанного времени, клиент должен как можно скорее попытаться получить блокировку от другого экземпляра Redis.
-
Клиент использует текущее время минус время начала получения блокировки (время, записанное на шаге 1), чтобы получить время, используемое для получения блокировки. Если и только если блокировка получена от большинства (N/2+1, здесь 3 узла) узлов Redis, а время использования меньше времени истечения срока действия блокировки, блокировка считается полученной успешно.
-
Если блокировка получена, реальное время действия ключа равно действительному времени минус время, использованное для получения блокировки (результат, рассчитанный на шаге 3).
-
Если по какой-либо причине получение блокировки не удается (блокировка не была получена по крайней мере в N/2+1 экземплярах Redis или время получения блокировки превысило допустимое время), клиент должен разблокировать все экземпляры Redis (используя Redis Lua). сценарий).
Процесс снятия блокировки относительно прост: клиент инициирует операцию снятия блокировки со всех узлов Redis, включая узел, который не блокируется, а также должен выполнить операцию снятия блокировки. описание алгоритма Почему?
Причина в том, что ответный пакет, возвращенный клиенту, может быть потерян после успешной блокировки определенного узла.Такая ситуация может возникнуть в асинхронной модели связи: связь между клиентом и сервером нормальная, но обратное направление проблематично. . Хотя для клиента блокировка не проходит из-за таймаута ответа, но для узла Redis успешное выполнение команды SET означает, что блокировка прошла успешно. Следовательно, при снятии блокировки клиент также должен инициировать запрос к тем узлам Redis, которым не удалось получить блокировку в это время.
Кроме того, чтобы избежать потери блокировки после перезапуска узла Redis, что повлияет на безопасность блокировки, антирез также предложил концепцию отложенного перезапуска, то есть после сбоя узла не перезапускать сразу , но подождите некоторое время перед перезапуском, это время должно быть больше, чем время действия блокировки.
Для более глубокого изучения Redlock заинтересованные друзья могут обратиться к следующим официальным документам:
Суммировать
Распределенная система предназначена для достижения баланса между сложностью и преимуществами, обеспечения максимальной безопасности и надежности, избегая чрезмерного проектирования. Redlock действительно может обеспечить более безопасные распределенные блокировки, но это также требует затрат, требующих большего количества узлов Redis. В реальном бизнесе распределенные блокировки на основе одноточечного Redis обычно используются для удовлетворения большинства потребностей. Иногда несоответствия данных могут быть устранены путем ручного вмешательства для дополнения данных. Соберитесь»! .