Что такое распределенная блокировка?

Redis задняя часть ZooKeeper Memcached






----- На следующий день -----
























————————————













Каковы реализации распределенных блокировок?


1. Распределенная блокировка Memcached


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


2. Распределенная блокировка Redis


Подобно Memcached, с использованием Redisкоманда setnx. Эта команда также является атомарной операцией, и установка может быть успешной только в том случае, если ключ не существует. (Команда setnx не идеальна, альтернативы будут представлены позже)


3. Распределенный замок Zookeeper


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


4.Chubby


Служба распределенных блокировок общего назначения, реализованная Google, использует алгоритм консенсуса Paxos внизу.







Как реализовать распределенные блокировки с помощью Redis?


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


1. Блокировка


Самый простой способ — использовать команду setnx. Ключ является уникальным идентификатором замка, который называется в соответствии с бизнесом. Например, если вы хотите заблокировать действие seckill товара, вы можете назвать ключ "lock_sale_commodity ID". И какое значение установлено? Мы можем временно установить его на 1. Псевдокод блокировки выглядит следующим образом:


setnx(ключ, 1)


Когда поток выполняет setnx и возвращает 1, это означает, что ключ изначально не существует, и поток успешно получает блокировку; когда поток выполняет setnx и возвращает 0, это означает, что ключ уже существует, и поток не может его захватить. замок.



2. Разблокировать


Если есть блокировка, то должна быть и разблокировка. Когда поток, получивший блокировку, завершает выполнение задачи, ему необходимо снять блокировку, чтобы другие потоки могли войти. Самый простой способ снять блокировку — выполнить инструкцию del, псевдокод выглядит следующим образом:


дел (ключ)


После снятия блокировки другие потоки могут продолжить выполнение команды setnx для получения блокировки.



3. Тайм-аут блокировки


Что означает тайм-аут блокировки? Если поток, получивший блокировку, зависает в процессе выполнения задачи, а явным образом снять блокировку слишком поздно, ресурс будет заблокирован навсегда, и другие потоки никогда не захотят в него входить.


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


истекает (ключ, 30)



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


если (setnx (ключ, 1) == 1) {

истекает (ключ, 30)

try {

do something ......

} finally {

дел (ключ)

}

}










Код такой хороший, зачем идти домой и ждать уведомления?


Потому что в приведенном выше псевдокоде есть три фатальные проблемы:


1. Неатомарность setnx и expire


Представьте себе экстремальный сценарий, когда поток выполняет setnx и успешно получает блокировку:




Setnx только что был успешно выполнен, и до того, как может быть выполнена команда expire, узел 1 Duang зависает.





Таким образом, блокировка не имеет установленного срока действия и становится «бессмертной», а другие потоки больше не могут получить блокировку.


Как это решить? Сама инструкция setnx не поддерживает входящий тайм-аут, к счастью, Redis 2.6.12 и более поздние версииsetДиректива добавляет необязательные параметры, псевдокод выглядит следующим образом:


установить(ключ, 1, 30, NX)


Это заменяет инструкцию setnx.



2. del вызывает ошибочное удаление


Другой крайний случай, когда поток успешно получает блокировку, а установленное время ожидания равно 30 секундам.




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




Затем поток A завершает выполнение задачи, а затем поток A выполняет команду del, чтобы снять блокировку. Но в это время поток B не закончил выполнение,Поток A фактически удаляет блокировку, добавленную потоком B..




Как избежать этой ситуации? Вы можете решить, прежде чем del снимет блокировку, чтобы проверить, является ли текущая блокировка добавленной вами.


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


Замок:

String threadId = Thread.currentThread().getId()

установить (ключ,threadId , 30, НХ)


Разблокировать:

если(threadId .equals(redisClient.get(key))){

del(key)

}


Однако при этом возникает новая проблема,Оценка и снятие блокировки — это две независимые операции, а не атомарные..


Все мы программисты, стремящиеся к совершенству, поэтому эта часть реализована с помощью Lua-скриптов:


String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";


redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));


Таким образом, процесс проверки и удаления является атомарным.



3. возможность параллелизма


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


Как это сделать? Мы можем позволить потоку, который получил блокировку, начатьНить демона, который используется для «пожизненной» блокировки, срок действия которой истекает.




Когда пройдет 29 секунд, а поток A не завершит выполнение, поток демона выполнит команду expire, чтобы «обновить» блокировку на 20 секунд. Поток демона начинает выполняться с 29-й секунды и выполняется каждые 20 секунд.




Когда поток A завершит выполнение задачи, он явно отключит поток демона.




В другом случае, если узел 1 внезапно отключится, поскольку поток A и поток демона находятся в одном и том же процессе, поток демона также остановится. Когда срок действия блокировки истекает, никто не продлевает ее жизнь, и она автоматически снимается.




Код потока демона реализовать не сложно.При общей идее можно попробовать реализовать самому.










Несколько дополнений:


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



----КОНЕЦ----



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