WeChat ищите и следите за публичной учетной записью «Капли воды и серебряные пули» и получайте высококачественную техническую галантерею как можно скорее. 7 лет исследований и разработок в области бэк-энда, демонстрирующих вам другую техническую перспективу.
Привет, меня зовут Кайто.
В этой статье я хочу поговорить с вами о «безопасности» распределенных блокировок Redis.
На тему распределенных блокировок Redis много статей написано плохо, зачем мне писать эту статью?
Потому что я обнаружил, что 99% статей в Интернете толком не проясняют этот вопрос. В результате многие читатели прочитали много статей, но все еще в тумане. Например, можете ли вы четко ответить на следующие вопросы?
- Как реализовать распределенную блокировку на основе Redis?
- Действительно ли распределенная блокировка Redis безопасна?
- Что не так с Redlock для Redis? Это точно безопасно?
- Индустрия спорит о Redlock, о чем они спорят? Какой взгляд правильный?
- Используют ли распределенные блокировки Redis или Zookeeper?
- Какие вопросы необходимо учитывать при реализации «отказоустойчивой» распределенной блокировки?
В этой статье я подробно разъясню эти вопросы.
Прочитав эту статью, вы получите не только полное представление о распределенных блокировках, но и более глубокое понимание «распределенных систем».
Статья немного длинновата, но много сухого, надеюсь, вы терпеливо ее прочитаете.
Зачем нужны распределенные блокировки?
Прежде чем мы начнем говорить о распределенных блокировках, необходимо кратко представить, зачем нам нужны распределенные блокировки?
Распределенной блокировке соответствует «одномашинная блокировка». Когда мы пишем многопоточные программы, мы избегаем проблем с данными, вызванных одновременной операцией общей переменной. Обычно блокировка используется для «взаимного исключения», чтобы гарантировать правильность общей переменной. , которая используется внутри «того же процесса».
Если он заменяется несколькими процессами, которым необходимо одновременно работать с общим ресурсом, то как сделать взаимоисключающим?
Например, текущие бизнес-приложения обычно представляют собой микросервисные архитектуры, что также означает, что приложение будет развертывать несколько процессов.Если этим нескольким процессам необходимо изменить одну и ту же строку записей в MySQL, чтобы избежать ошибок данных, вызванных -order. На этом этапе нам нужно ввести «распределенные блокировки», чтобы решить эту проблему.
Для достижения распределенных блокировок необходимо использовать внешнюю систему, и все процессы направляются в эту систему для подачи заявки на «блокировку».
И в этой внешней системе должна быть реализована возможность «взаимного исключения», то есть одновременно приходят два запроса, только один процесс вернет успех, а другой вернет отказ (или ожидание).
Этой внешней системой может быть MySQL, Redis или Zookeeper. Но в погоне за лучшей производительностью мы обычно выбираем для этого Redis или Zookeeper.
Далее я возьму Redis в качестве основной линии, от мелкой до глубокой, и предложу вам глубокий анализ различных проблем «безопасности» распределенных блокировок, чтобы помочь вам досконально понять распределенные блокировки.
Как реализовать распределенную блокировку?
Начнем с самого простого.
Для реализации распределенных блокировок Redis должен иметь возможности «взаимного исключения».Мы можем использовать команду SETNX, что означаетSET if Not eXists, то есть если ключ не существует, будет установлено его значение, иначе ничего не будет сделано.
Два клиентских процесса могут выполнить эту команду для достижения взаимного исключения, и может быть реализована распределенная блокировка.
Клиент 1 подает заявку на блокировку, и блокировка выполняется успешно:
127.0.0.1:6379> SETNX lock 1
(integer) 1 // 客户端1,加锁成功
Клиент 2 подает заявку на блокировку, и поскольку он прибывает позже, блокировка не выполняется:
127.0.0.1:6379> SETNX lock 1
(integer) 0 // 客户端2,加锁失败
На этом этапе успешно заблокированный клиент может управлять «общим ресурсом», например, изменять строку данных MySQL или вызывать запрос API.
После завершения операции блокировку следует снять вовремя, чтобы дать опоздавшему возможность управлять общими ресурсами. Как снять блокировку?
Это также очень просто, просто используйте команду DEL, чтобы удалить ключ:
127.0.0.1:6379> DEL lock // 释放锁
(integer) 1
Логика очень проста, и общий путь выглядит следующим образом:
Однако у него есть большая проблема: когда клиент 1 получает блокировку, если происходит следующий сценарий, это вызовет «взаимную блокировку»:
- Программа обрабатывает исключения бизнес-логики и не снимает блокировку вовремя
- Процесс зависает и не имеет возможности снять блокировку
В это время клиент всегда будет занимать блокировку, а другие клиенты «никогда» не получат блокировку.
Как решить эту проблему?
Как избежать тупика?
Решение, которое мы можем легко придумать, состоит в том, чтобы установить «период аренды» для замка при подаче заявки на замок.
При реализации в Redis для этого ключа нужно установить «время истечения срока действия». Здесь мы предполагаем, что время работы общего ресурса не будет превышать 10 с, тогда при блокировке установите срок действия ключа через 10 с:
127.0.0.1:6379> SETNX lock 1 // 加锁
(integer) 1
127.0.0.1:6379> EXPIRE lock 10 // 10s后自动过期
(integer) 1
Таким образом, независимо от того, является ли клиент ненормальным или нет, блокировка может быть «автоматически снята» через 10 секунд, и другие клиенты все еще могут получить блокировку.
Но действительно ли это нормально?
Все еще есть проблемы.
Текущая операция, блокировка и установка срока действия - это 2 команды.Возможно ли, что только первая команда выполняется, а вторая команда "слишком поздно"? Например:
- Выполнение SETNX успешно, но выполнение завершается ошибкой из-за проблем с сетью при выполнении EXPIRE.
- SETNX выполнен успешно, Redis аварийно отключен, EXPIRE не может выполниться
- SETNX выполняется успешно, клиент аварийно завершает работу, а EXPIRE не может выполниться.
Короче говоря, нельзя гарантировать, что эти две команды являются атомарными операциями (совместный успех), и существует потенциальный риск того, что установка времени истечения не будет выполнена, и проблема «взаимной блокировки» все еще будет возникать.
Как сделать?
До версии Redis 2.6.12 нам нужно было сделать все возможное, чтобы обеспечить атомарное выполнение SETNX и EXPIRE, а также подумать, как поступать с различными исключениями.
Но после Redis 2.6.12 Redis расширил параметры команды SET, и с этой командой все в порядке:
// 一条命令保证原子性执行
127.0.0.1:6379> SET lock 1 EX 10 NX
OK
Это решает проблему взаимоблокировки и относительно просто.
Давайте посмотрим на анализ, в чем проблема с ним?
Рассмотрим такой сценарий:
- Клиент 1 успешно заблокирован и начинает работать с общими ресурсами
- Время работы клиента 1 с общим ресурсом «превышает» время истечения срока действия блокировки, и блокировка «автоматически снимается».
- Клиент 2 успешно блокируется и начинает работать с общими ресурсами
- Клиент 1 завершает операцию с общим ресурсом и снимает блокировку (но блокировка клиента 2 снимается)
Видите ли, здесь есть две серьезные проблемы:
- срок действия блокировки истек: Клиенту 1 требуется слишком много времени для работы с общим ресурсом, в результате чего блокировка автоматически снимается, а затем удерживается клиентом 2.
- открыть чужой замок: после того, как клиент 1 завершит операцию с общим ресурсом, он снимает блокировку клиента 2.
В чем причина этих двух проблем? Давайте посмотрим на них один за другим.
Первая проблема может быть вызвана неточностью нашей оценки времени эксплуатации общих ресурсов.
Например, «самое медленное» время работы с общим ресурсом может занять 15 секунд, но мы устанавливаем время истечения только на 10 секунд, поэтому существует риск преждевременного истечения срока действия блокировки.
Если время истечения слишком короткое, то увеличьте избыточное время, например, установите время истечения на 20 с, всегда ли это нормально?
Это действительно может «смягчить» проблему и снизить вероятность проблем, но все же не может «полностью решить» проблему.
Почему?
Причина в том, что после получения клиентом блокировки при работе с общим ресурсом может возникнуть очень сложный сценарий, например, внутри программы возникает исключение, время ожидания сетевого запроса и т.д.
Поскольку это «оценочное» время, его можно рассчитать только приблизительно, если только вы не можете предсказать и охватить все сценарии, которые приводят к увеличению времени, но на самом деле это очень сложно.
Есть ли лучшее решение?
Не волнуйтесь, об этой проблеме я подробно объясню соответствующее решение позже.
Перейдем ко второму вопросу.
Вторая проблема заключается в том, что один клиент снимает блокировки, удерживаемые другими клиентами.
Подумайте об этом, что является ключевым моментом, который приводит к этой проблеме?
Дело в том, что когда каждый клиент снимает блокировку, это "безмозглая" операция, и не проверяется "держится ли блокировка на себе", поэтому есть риск снять чужие блокировки.Такой процесс разблокировки, Очень не "строгий"!
Как решить эту проблему?
Что делать, если замок снят кем-то другим?
Решение: когда клиент блокируется, установите «уникальный идентификатор», который знает только он.
Например, это может быть собственный идентификатор потока, или это может быть UUID (случайный и уникальный), здесь мы возьмем UUID в качестве примера:
// 锁的VALUE设置为UUID
127.0.0.1:6379> SET lock $uuid EX 20 NX
OK
Здесь предполагается, что 20-секундного времени разделения операций вполне достаточно, а проблема автоматического истечения блокировки не рассматривается.
После этого, при снятии блокировки, вы должны сначала определить, возвращается ли вам блокировка.Псевдокод можно записать так:
// 锁是自己的,才释放
if redis.get("lock") == $uuid:
redis.del("lock")
Здесь для снятия блокировки используются две команды GET + DEL.В этот раз снова возникнет атомарная проблема, о которой мы упоминали ранее.
- Клиент 1 выполняет GET и решает, что блокировка принадлежит ему.
- Клиент 2 выполняет команду SET, чтобы принудительно получить блокировку (хотя вероятность возникновения относительно низкая, нам необходимо тщательно рассмотреть модель безопасности блокировки)
- Клиент 1 выполняет DEL, но снимает блокировку клиента 2.
Видно, что эти две команды по-прежнему должны выполняться атомарно.
Как это сделать атомарно? Луа скрипт.
Мы можем написать эту логику в виде сценария Lua и позволить Redis выполнить ее.
Поскольку Redis обрабатывает каждый запрос «однопоточно», при выполнении Lua-скрипта другие запросы должны ждать, пока Lua-скрипт не будет обработан, чтобы никакие другие команды не вставлялись между GET + DEL.
Сценарий Lua для безопасного снятия блокировки выглядит следующим образом:
// 判断锁是自己的,才释放
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end
Таким образом, весь процесс блокировки и разблокировки становится более «строгим».
Здесь мы сначала суммируем распределенную блокировку на основе Redis, строгий процесс выглядит следующим образом:
- Блокировка: SET $lock_key $unique_id EX $expire_time NX
- Управление общими ресурсами
- Снимите блокировку: Lua-скрипт, сначала GET, чтобы определить, принадлежит ли блокировка самому себе, а затем DEL, чтобы снять блокировку.
Что ж, с этой полной моделью замка давайте вернемся к первому вопросу, упомянутому ранее.
Что делать, если срок действия блокировки оценивается неправильно?
Что делать, если срок действия блокировки оценивается неправильно?
Как мы упоминали ранее, если время истечения блокировки не будет правильно оценено, у блокировки будет риск «раннего» истечения срока действия.
Компромиссное решение, предложенное в то время, состояло в том, чтобы попытаться «зарезервировать» время истечения срока действия, чтобы уменьшить вероятность преждевременного истечения срока действия блокировки.
На самом деле это решение не решает проблему идеально, так что же нам делать?
Можно ли сконструировать такую схему:При блокировке сначала устанавливаем время истечения срока, а затем запускаем «поток демона», чтобы регулярно определять время истечения срока действия блокировки. будет автоматически «обновляться». », чтобы сбросить время истечения срока действия.
Это действительно лучшее решение.
К счастью, если вы работаете с техническим стеком Java, у вас уже есть библиотека, которая выполняет всю эту работу:Redisson.
Redisson — это клиент Redis SDK, реализованный на языке Java. При использовании распределенных блокировок он использует схему «автоматического обновления», чтобы избежать истечения срока действия блокировки. Обычно мы называем этот поток демона потоком «сторожевой таймер».
Кроме того, этот SDK также инкапсулирует множество простых в использовании функций:
- повторная блокировка
- оптимистическая блокировка
- честный замок
- Блокировка чтения-записи
- Redlock (красный замок, подробно будет описан ниже)
API, предоставляемый этим SDK, очень удобен и может работать с распределенными блокировками так же, как и с локальными блокировками. Если вы представляете стек технологий Java, вы можете использовать его напрямую.
Мы не будем заострять внимание на использовании Redisson здесь, вы можете прочитать официальный Github, чтобы узнать, как его использовать, что относительно просто.
Здесь мы снова подведем итоги, реализацию распределенных блокировок на основе Redis, проблемы, с которыми столкнулись ранее, и соответствующие решения:
- тупик: Установите время истечения срока действия
- Время истечения плохо оценивается, и срок действия блокировки истекает раньше: поток демона, автообновление
- Блокировка снята кем-то другим: Блокировка записывается с уникальным идентификатором, и блокировка снимается сначала проверкой идентификатора, а затем отпусканием
Какие другие проблемные сценарии могут поставить под угрозу безопасность замков Redis?
Сценарии, проанализированные ранее, представляют собой все проблемы, которые могут возникнуть из-за блокировки «одного» экземпляра Redis, и не включают детали архитектуры развертывания Redis.
И когда мы используем Redis, мы обычно используемКластер Master-Slave + SentinelПреимущество этого заключается в том, что когда основная библиотека выходит из строя аварийно, дозорный может реализовать «автоматическое переключение при сбое», продвигать подчиненную библиотеку к основной библиотеке и продолжать предоставлять услуги для обеспечения доступности.
Когда произойдет «переключение ведущий-ведомый», будет ли эта распределительная блокировка по-прежнему безопасной?
Рассмотрим этот сценарий:
- Клиент 1 выполняет команду SET в основной базе данных, и блокировка прошла успешно.
- В этот момент основная библиотека аварийно отключена, и команда SET не была синхронизирована с подчиненной библиотекой (репликация ведущий-подчиненный выполняется асинхронно).
- Подчиненная библиотека была переведена Стражем в новую основную библиотеку, которая заблокирована в новой основной библиотеке и потеряна!
Видно, что при введении копии Redis блокировка распространения все еще может быть затронута.
Как решить эту проблему?
Для этого автор Redis предлагает решение, которое мы часто слышимРедлок.
Действительно ли это решает вышеуказанную проблему?
Редлок действительно безопасен?
Ну и наконец к главному пункту этой статьи. А? Так много проблем, упомянутых выше, являются ли они просто основами?
Да, это только закуски, настоящие тяжелые блюда, только начали.
Если вы не понимаете содержание, упомянутое выше, я предлагаю вам прочитать его еще раз и сначала разъяснить основной процесс блокировки и разблокировки.
Если вы уже что-то знаете о Redlock, вы можете подписаться на меня здесь, чтобы просмотреть его снова.Если вы не знаете Redlock, ничего страшного, я познакомлю вас с ним снова.
Стоит напомнить вам, что,Позже я не только расскажу о принципе работы Redlock, но и приведу множество вопросов о «распределенных системах», вам лучше следовать моим идеям и анализировать ответы на вопросы в уме.
Теперь посмотрим, как решение Redlock, предложенное автором Redis, решает проблему сбоя блокировки после переключения master-slave.
Решение Redlock основано на двух предпосылках:
- Нет необходимости развертывать большеиз библиотекиа такжечасовойэкземпляр, только развертываниеосновная библиотека
- Однако необходимо развернуть несколько основных библиотек, и официально рекомендуется не менее 5 экземпляров.
То есть, если вы хотите использовать Redlock, вам нужно развернуть как минимум 5 экземпляров Redis, все из которых являются основной библиотекой, и между ними нет никакой связи, все они являются изолированными экземплярами.
Примечание. Либо разверните Redis Cluster, либо разверните 5 простых экземпляров Redis.
Как именно используется Redlock?
Общий процесс выглядит следующим образом, который разделен на 5 шагов:
- Сначала клиент получает «текущую метку времени T1».
- Клиент инициирует запросы на блокировку этих пяти экземпляров Redis по очереди (используя команду SET, упомянутую выше), и каждый запрос устанавливает тайм-аут (на уровне миллисекунд, намного меньше, чем время действия блокировки). сбой (включая тайм-аут сети, блокировки, удерживаемые другими и т. д.), немедленно применить блокировку к следующему экземпляру Redis.
- Если клиент успешно блокирует >=3 (большинство) экземпляров Redis, он снова получает «текущую отметку времени T2». Если T2 - T1
- После успешной блокировки используйте общий ресурс (например, измените строку в MySQL или инициируйте запрос API).
- Если блокировка не работает, запрос на снятие блокировки выдается «всем узлам» (упомянутый выше сценарий Lua снимает блокировку).
Позвольте мне кратко резюмировать для вас, есть 4 ключевых момента:
- Клиент подает заявку на блокировку нескольких экземпляров Redis.
- Необходимо убедиться, что большинство узлов успешно заблокировано
- Общее время, затрачиваемое на блокировку большинства узлов, меньше времени истечения срока действия, установленного блокировкой.
- Чтобы снять блокировку, необходимо отправить запрос на снятие блокировки всем узлам.
Когда вы читаете его в первый раз, это может быть непросто понять, поэтому рекомендуется прочитать приведенный выше текст несколько раз, чтобы углубить свою память.
Затем очень важно запомнить эти шаги 5. Далее будут проанализированы различные проблемы и предположения, которые могут привести к сбою блокировки в соответствии с этим процессом.
Хорошо, теперь, когда мы понимаем процесс Redlock, давайте посмотрим, почему Redlock это делает.
1) Зачем блокировать несколько экземпляров?
По сути, это для «отказоустойчивости», некоторые экземпляры аварийно отключены, остальные успешно заблокированы, а вся служба блокировки по-прежнему доступна.
2) Почему считается успешным, если большая часть блокировок прошла успешно?
Когда несколько экземпляров Redis используются вместе, они фактически образуют «распределенную систему».
В распределенной системе всегда будут «аномальные узлы», поэтому, говоря о проблемах распределенной системы, необходимо учитывать, сколько существует ненормальных узлов, и это все равно не повлияет на «правильность» всей системы.
Это проблема «отказоустойчивости» распределенной системы, и вывод этой проблемы таков:Если есть только «неисправные» узлы, при условии, что большинство узлов исправны, вся система может по-прежнему предоставлять правильные услуги.
Моделью этой проблемы является задача «византийского генерала», о которой мы часто слышим.Если вам интересно, вы можете посмотреть процесс вывода алгоритма.
3) Почему необходимо рассчитывать совокупное время, затраченное на блокировку после успешной блокировки на шаге 3?
Поскольку работает несколько узлов, это определенно займет больше времени, чем работа с одним экземпляром.Более того, поскольку это сетевой запрос, сетевая ситуация усложняется, и могут бытьзадержка, потеря пакетов, тайм-аутКогда возникает ситуация, чем больше сетевых запросов, тем выше вероятность ненормального возникновения.
Следовательно, даже если большинство узлов успешно заблокировано, если совокупное время блокировки «превысит» время истечения срока действия блокировки, тогда срок действия блокировки на некоторых экземплярах может истек в это время, и блокировка не имеет смысла.
4) Зачем снимать блокировку и управлять всеми узлами?
Когда узел Redis заблокирован, блокировка может завершиться ошибкой из-за «сетевых причин».
Например, клиент успешно блокирует экземпляр Redis, но при чтении результата ответа возникают проблемы с сетью.чтение не удалось, то блокировка на Redis действительно успешно заблокирована.
Следовательно, при снятии блокировки, независимо от того, была ли блокировка ранее успешно заблокирована, необходимо снять блокировки «всех узлов», чтобы гарантировать очистку «остаточных» блокировок на узлах.
Что ж, я понимаю процесс и связанные с ним проблемы Redlock.Кажется, Redlock действительно решил проблему аварийного отключения узлов Redis и выхода из строя замков, и обеспечил "безопасность" замков.
Но так ли это на самом деле?
Кто прав, а кто виноват в дебатах о Рэдлоке?
Как только автор Redis предложил это решение, оно сразу же было принято известными экспертами по распределенным системам в отрасли.вопрос!
Этот специалист называетсяMartin, исследователь распределенных систем в Кембриджском университете, Великобритания. До этого он был инженером-программистом и предпринимателем, работавшим над крупномасштабной инфраструктурой данных. Он также часто выступает на конференциях, ведет блоги, пишет книги и является участником открытого исходного кода.
Он тут же написал статью, ставя под сомнение проблему модели алгоритма Redlock, и выдвинул свои взгляды на проектирование распределенных замков.
Впоследствии автор Redis Антирез, чтобы не отставать перед лицом сомнений, также написал статью, опровергающую точку зрения другой стороны, и подробно проанализировал дополнительные детали модели алгоритма Redlock.
Более того, дискуссия по этому вопросу также вызвала в то время очень бурное обсуждение в Интернете.
У обоих есть четкие идеи и достаточные аргументы.Это мастерский ход, а также очень хорошее столкновение идей в области распределенных систем! Обе стороны являются экспертами в области распределенных систем, но делают много противоположных утверждений по одному и тому же вопросу.Что происходит?
Ниже я извлеку важные моменты из их дебатов и представлю их вам.
Напоминание: объем информации, приведенной ниже, очень велик и может быть непонятен. Лучше замедлить чтение и прочитать.
Распространённые сомнения эксперта Мартина по поводу Relock
В своей статье он в основном подробно остановился на 4 аргументах:
1) Какова цель распределенных блокировок?
Мартин сказал, что вы должны сначала понять, какова цель использования распределенных блокировок?
Он видит две цели.
Во-первых, эффективность.
Использование возможности взаимного исключения распределенных блокировок позволяет избежать ненужного повторного выполнения одной и той же работы (например, некоторых дорогостоящих вычислительных задач). Если блокировка не сработает, это не повлечет за собой «вредных» последствий, таких как отправка 2-х писем, что безвредно.
Во-вторых, правильность.
Блокировки используются для предотвращения взаимодействия параллельных процессов друг с другом. Если блокировка не сработает, это приведет к тому, что несколько процессов будут обрабатывать одни и те же данные одновременно, а последствияСерьезные ошибки данных, постоянные несоответствия, потеря данныхТакие злокачественные проблемы, как введение пациентам повторных доз лекарств, имеют серьезные последствия.
Он считает, что если вы за первое — эффективность, то можете использовать автономную версию Redis, даже если сбой блокировки (даунтайм, переключение master-slave) будет происходить изредка, это не будет иметь серьезных последствий. А использовать Redlock слишком тяжело и ненужно.
А если для корректности, то Мартин считает, что Redlock вообще не может соответствовать требованиям безопасности, и проблема выхода из строя блокировки все же есть!
2) Проблемы с блокировками в распределенных системах
Мартин сказал, что распределенная система больше похожа на сложного «зверя», со всякими аномалиями, о которых и не придумаешь.
Эти аномальные сценарии в основном включают три основных блока, которые также являются тремя горами, с которыми столкнутся распределенные системы:NPC.
- N: сетевая задержка, сетевая задержка
- P: пауза процесса, пауза процесса (GC)
- C: дрейф часов, дрейф часов
Мартин указывает на проблему безопасности Redlock на примере приостановки процесса (GC):
- Клиент 1 запрашивает блокировку узлов A, B, C, D, E.
- После того, как клиент 1 получает блокировку, он входит в GC (это занимает много времени)
- Срок действия блокировок истек на всех узлах Redis
- Клиент 2 получает блокировки на A, B, C, D, E
- Сборка клиента 1 завершена, блокировка считается успешной
- Клиент 2 также считает, что блокировка получена, и возникает «конфликт».
Мартин считает, что GC может произойти в любой точке программы, а время выполнения не поддается контролю.
Примечание: Конечно, даже если вы используете язык программирования без GC, когда возникают сетевые задержки и дрейф часов, это может вызвать проблемы с Redlock.Здесь Мартин просто берет GC в качестве примера.
3) Неразумно предполагать, что часы правильные
Или, когда у нескольких узлов Redis возникают проблемы, это также вызывает Redlock.отказ блокировки.
- Клиент 1 получает блокировки на узлах A, B, C, но не может получить доступ к D и E из-за проблем с сетью.
- Часы на узле C «скачут вперед», в результате чего истечет срок действия блокировки.
- Клиент 2 блокирует узлы C, D, E и не может получить доступ к узлам A и B из-за проблем с сетью.
- Оба клиента 1 и 2 теперь считают, что они держат блокировку (конфликт)
Мартин считает, что Redlock должен «сильно полагаться» на синхронизацию часов нескольких узлов.Если часы узла неверны, модель алгоритма не сработает.
Аналогичная проблема возникает, даже если C не является скачком часов, а «перезапускается сразу после сбоя».
Далее Мартин объяснил, что весьма вероятно, что часы машины пойдут не так:
- Системный администратор «вручную модифицировал» часы машины
- Машинные часы сделали большой «скачок» при синхронизации времени NTP
Короче говоря, Мартин считает, что алгоритм Редлока основан на «модели синхронизации», и есть много исследований данных, показывающих, что предположение о модели синхронизации проблематично в распределенных системах.
В запутанной распределенной системе вы не можете предположить, что системные часы идут правильно, поэтому вы должны быть очень осторожны со своими предположениями.
4) Предложите схему токенов для обеспечения правильности
Соответственно, Мартин предложил схему, называемую fecing token, для обеспечения правильности распределенных блокировок.
Поток модели выглядит следующим образом:
- Когда клиент получает блокировку, служба блокировки может предоставить «увеличенный» токен.
- Клиент использует этот токен для работы с общим ресурсом.
- Общие ресурсы могут отклонять запросы от «опоздавших» на основе токенов.
Таким образом, независимо от того, какая нештатная ситуация возникает в NPC, безопасность распределенной блокировки может быть гарантирована, поскольку она построена на «асинхронной модели».
И Redlock не может предоставить схему, подобную фейковому токену, поэтому не может гарантировать безопасность.
Он также сказал:Хорошая распределенная блокировка, как бы ни происходил НПЦ, может дать результат в течение заданного времени, но она не даст неправильного результата. То есть влияет только на "работоспособность" (или активность) блокировки, но не на ее "правильность".
Вывод Мартина:
1. Редлок невзрачный: Для эффективности Redlock тяжелее, в этом нет необходимости, а для корректности Redlock недостаточно безопасен.
2. Предположение о часах неразумно: Алгоритм делает опасные предположения о системных часах (предполагая, что часы нескольких машинных узлов согласованы), и если эти предположения не выполняются, блокировка не будет выполнена.
3. Нет гарантии правильности: Redlock не может предоставить решение, аналогичное токену ограждения, поэтому не может решить проблему корректности. Для корректности используйте программное обеспечение с «системой консенсуса», такое как Zookeeper.
Хорошо, это аргумент Мартина против использования Redlock, и он выглядит правдоподобным.
Давайте посмотрим, как автор Redis Антирез опровергает это.
Опровержение от автора Redis Antirez
В статье автора Redis есть 3 ключевых момента:
1) Объясните проблему с часами
Во-первых, автор Redis с первого взгляда разобрался с основными вопросами, поднятыми другой стороной:проблема с часами.
Автор Redis заявил, что Redlock не нуждается в полностью согласованных часах, а должен быть только примерно согласованным, допуская «ошибки».
Например, если вы хотите отсчитывать время 5 с, но на самом деле вы можете вспомнить 4,5 с, а затем 5,5 с, есть определенная ошибка, но пока она не превышает «диапазон ошибок» времени сбоя блокировки, такие часы требования к точности не очень высоки, и это также соответствует реальной среде.
Что касается проблемы «модификации часов», упомянутой другой стороной, автор Redis возразил:
- Вручную изменить часы: Только не делайте этого, иначе, если вы измените лог Raft напрямую, Raft не будет работать...
- скачок часов: Путем "правильной эксплуатации и обслуживания" гарантируется, что часы машины не скачут значительно (каждый раз это делается с помощью небольших корректировок), на самом деле это можно сделать
Redis Авторы объясняют, почему проблема с часами? Потому что в конце процесса опровержения необходимость полагаться на эту основу для дальнейшего объяснения.
2) Объясните сетевую задержку, проблемы GC
После этого автор Redis также опроверг поднятую другой стороной проблему о том, что задержка сети и процесс GC могут привести к сбою Redlock:
Давайте вернемся к проблемным предположениям, которые выдвинул Мартин:
- Клиент 1 запрашивает блокировку узлов A, B, C, D, E.
- После того, как клиент 1 получает блокировку, он входит в GC
- Срок действия блокировок истек на всех узлах Redis
- Клиент 2 получает блокировки на узлах A, B, C, D, E
- Сборка клиента 1 завершена, блокировка считается успешной
- Клиент 2 также считает, что блокировка получена, и возникает «конфликт».
Автор Redis возразил, что это предположение на самом деле проблематично, и Redlock может гарантировать безопасность блокировки.
Что тут происходит?
Помните 5 шагов, которые мы представили ранее в процессе Redlock? Я принесу его сюда, чтобы вы рассмотрели.
- Сначала клиент получает «текущую метку времени T1».
- Клиент инициирует запросы на блокировку этих пяти экземпляров Redis по очереди (используя команду SET, упомянутую выше), и каждый запрос устанавливает тайм-аут (на уровне миллисекунд, намного меньше, чем время действия блокировки). сбой (включая тайм-аут сети, блокировки, удерживаемые другими и т. д.), немедленно применить блокировку к следующему экземпляру Redis.
- Если клиент успешно заблокируется более чем из 3 (большинства) экземпляров Redis, он снова получит «текущую метку времени T2». Если T2 - T1
- После успешной блокировки используйте общий ресурс (например, измените строку в MySQL или инициируйте запрос API).
- Если блокировка не работает, запрос на снятие блокировки выдается «всем узлам» (упомянутый выше сценарий Lua снимает блокировку).
Обратите внимание, что ключевым моментом является 1-3. На шаге 3, почему вам нужно повторно получить «текущую временную метку T2» после успешной блокировки? Также использовать время T2 - T1 для сравнения со временем истечения блокировки?
Автор Redis подчеркивает: если возникают нештатные ситуации, такие как задержка в сети, процесс GC и т. д., которые занимают много времени в 1-3 раза, это можно обнаружить на третьем шаге T2 — T1.Если время истечения блокировки превышена настройка, то просто подумайте, что блокировка не удастся, а затем снимите блокировки всех узлов просто отлично!
Автор Redis продолжает обсуждать, что если другая сторона считает, что сетевая задержка и процесс GC произошли после шага 3, то есть клиент подтвердил, что он получил блокировку, и возникла проблема в способе работы с общим ресурс, вызывающий сбой блокировки, то этоНе только проблема Redlock, у любого другого сервиса блокировки, например у Zookeeper, есть подобные проблемы, это выходит за рамки обсуждения.
Здесь я привожу пример, чтобы объяснить проблему:
- Клиент успешно получил блокировку через Redlock (прошел логику успешной блокировки большинства узлов и трудоемкой проверки блокировки)
- Клиент начинает работать с общим ресурсом, в это время сетевая задержка, процесс GC и т.д. занимают много времени.
- В этот момент блокировка истекает и автоматически снимается.
- Клиент начинает работать с MySQL (блокировка в это время может быть получена другими, и блокировка становится недействительной)
Вывод автора Redis здесь таков:
- Независимо от того, с какой трудоемкой проблемой сталкивается клиент перед получением блокировки, Redlock может обнаружить ее на шаге 3.
- После того, как клиент получает блокировку, происходит NPC, и ни Redlock, ни Zookeeper ничего сделать не могут.
Поэтому автор Redis считает, что Redlock может гарантировать правильность на основе обеспечения правильности часов.
3) Задайте вопрос о механизме ограждения токенов
Автор Redis также выразил сомнение в предложенном другой стороной механизме фецирования токенов, который в основном делится на 2 вопроса.Это самый неуместный для понимания.Пожалуйста, следите за ходом моей мысли.
ПервыйЭтот раствор должен потребовать управлять «Shared Resource Server», чтобы иметь возможность отклонить «старые токены».
Например, для работы с MySQL нужно получить токен с возрастающим номером от службы блокировки, а затем клиенту нужно взять этот токен для изменения определенной строки MySQL, что требует использования MySQL «изоляции вещей».
// 两个客户端必须利用事物和隔离性达到目的
// 注意 token 的判断条件
UPDATE table T SET val = $new_val, current_token = $token WHERE id = $id AND current_token < $token
Но что, если это не MySQL? Например, чтобы записать файл на диск или инициировать HTTP-запрос, то это решение бессильно, что предъявляет повышенные требования к серверу ресурсов для работы.
Другими словами, большинство используемых серверов ресурсов не имеют этой возможности взаимного исключения.
Кроме того, поскольку серверы ресурсов имеют возможность «взаимного исключения», зачем им нужны распределенные блокировки?
Поэтому автор Redis считает эту схему несостоятельной.
второй, чтобы сделать шаг назад, даже если Redlock не предоставляет возможности ограждения токена, но Redlock предоставил случайное значение (то есть Uuid, упомянутый выше), используя это случайное значение, вы также можете достичь того же эффекта, что и ограждение токен.
Как это сделать?
Автор Redis только упомянул, что аналогичные функции токена fecing могут быть выполнены, но он не расширил соответствующие детали.Согласно информации, которую я проверил, примерный процесс должен быть следующим.Если есть какая-либо ошибка, добро пожаловать в сообщение ~
- Клиент использует Redlock для получения блокировки
- Прежде чем клиент начнет работать с общим ресурсом, он сначала помечает ЗНАЧЕНИЕ блокировки на общем ресурсе, который будет использоваться.
- Клиент обрабатывает бизнес-логику и, наконец, при изменении общего ресурса оценивает, является ли тег таким же, как раньше, и модифицирует только его (аналогично идее CAS).
Или возьмем MySQL в качестве примера, пример такой:
- Клиент использует Redlock для получения блокировки
- Прежде чем клиент захочет изменить строку данных в таблице MySQL, он сначала обновляет ЗНАЧЕНИЕ блокировки до поля в этой строке (при условии, что здесь есть поле current_token)
- Клиент обрабатывает бизнес-логику
- Клиент изменяет эту строку данных MySQL, обрабатывает VALUE как условие WHERE, а затем изменяет
UPDATE table T SET val = $new_val WHERE id = $id AND current_token = $redlock_value
Можно видеть, что эта схема опирается на механизм транзакций MySQL, а также достигает того же эффекта, что и токен ограждения, упомянутый другой стороной.
Но тут есть небольшая проблема, которую подняли пользователи сети, когда участвовали в обсуждении вопроса:По этой схеме два клиента сначала «отмечают», а затем «проверяют + изменяют» общие ресурсы, поэтому нельзя гарантировать порядок операций двух клиентов?
И с упомянутым Tecing Token Martin, потому что токен представляет собой монотонно увеличивающиеся числа, ресурсный сервер может отказаться от небольшого запроса токена, чтобы убедиться, что «последовательная» операция!
Автор Redis объяснил этот вопрос по-другому, что, я думаю, имеет смысл.Он объяснил:Суть распределенных блокировок заключается во «взаимном исключении», если они могут гарантировать, что когда два клиента работают одновременно, один успешно, а другой нет, и нет необходимости заботиться о «последовательности».
В предыдущем вопросе от Мартина он всегда беспокоился об этой последовательной проблеме, но у автора Redis другое мнение.
Подводя итог, вывод автора Redis:
1. Автор согласен с другой стороной в отношении влияния «скачка часов» на Redlock, но считает, что скачка часов можно избежать, в зависимости от инфраструктуры, эксплуатации и обслуживания.
2. Redlock полностью учел проблему NPC в своем дизайне.Наличие NPC до шага 3 Redlock может обеспечить правильность блокировки, но появление NPC после шага 3 это проблема не только Redlock, но и других сервисы распределенных блокировок. , так что это не входит в рамки обсуждения.
Вы находите это интересным?
В распределенных системах небольшой замок, но все же многие проблемы, которые вы можете столкнуться с сценой, повлиять на его безопасность!
Я не знаю, с какой стороной вы больше согласны после прочтения мнений обеих сторон?
Не беспокойтесь, я объединю приведенные выше аргументы и позже расскажу о своем собственном понимании.
Что ж, после разговора о дебатах о распределенных блокировках Redis между двумя сторонами вы, возможно, заметили, что в своей статье Мартин рекомендует использовать Zookeeper для реализации распределенных блокировок, думая, что это более безопасно, так ли это?
Безопасны ли замки на основе Zookeeper?
Если вы узнали о Zookeeper, распределенная блокировка на его основе выглядит так:
- Клиенты 1 и 2 пытаются создать "временные узлы", такие как /lock
- Предполагая, что клиент 1 прибывает первым, блокировка выполняется успешно, а клиент 2 не может заблокировать
- Клиент 1 работает на общих ресурсах
- Клиент 1 удаляет узел /lock и снимает блокировку
Вы также должны видеть, что Zookeeper не похож на Redis, который должен учитывать время истечения блокировки.Он использует «временный узел», чтобы гарантировать, что после того, как клиент 1 получит блокировку, пока соединение продолжается, блокировку можно будет снять. держался все время.
Кроме того, если клиент 1 аварийно завершает работу, этот временный узел будет автоматически удален, гарантируя, что блокировка будет снята.
Да, нет проблем с истечением срока действия блокировки, и он может автоматически снимать блокировку, когда это ненормально.Как вы думаете, это идеально?
вообще-то нет.
Подумайте об этом, после того как клиент 1 создаст временный узел, как Zookeeper гарантирует, что этот клиент всегда удерживает блокировку?
Причина в том, чтоВ это время клиент 1 будет поддерживать сеанс с сервером Zookeeper, и этот сеанс будет полагаться на «синхронизированное сердцебиение» клиента для поддержания соединения.
Если Zookeeper долго не может получить пульс клиента, он считает, что сессия истекла, и временная нода также будет удалена.
Точно так же, основываясь на этом вопросе, мы также обсудим, как проблема GC влияет на блокировки Zookeeper:
- Клиент 1 успешно создал временный узел /lock и получил блокировку
- Клиент 1 имеет длинный сборщик мусора
- Клиент 1 не может отправить пульс в Zookeeper, и Zookeeper «удаляет» временный узел
- Клиент 2 успешно создал временный узел /lock и получил блокировку
- Клиент 1 GC завершает работу, но все еще думает, что удерживает блокировку (конфликт)
Видно, что даже при использовании Zookeeper безопасность процесса GC и аномальная сетевая задержка не могут быть гарантированы.
Это то, о чем упоминал автор Redis в статье опровержения выше: если клиент получил блокировку, но клиент и сервер блокировки «разъединяются» (например, GC), то проблема не только в Redlock, но и в других службах блокировки. есть аналогичная проблема, Zookeeper такой же!
Итак, здесь мы можем сделать вывод:Распределенная блокировка в крайних случаях не обязательно безопасна.
Если ваши бизнес-данные очень конфиденциальны, вы должны обратить внимание на эту проблему при использовании распределенных блокировок.Вы не можете предполагать, что распределенные блокировки на 100% безопасны.
Хорошо, а теперь суммируем преимущества и недостатки Zookeeper при использовании распределенных блокировок:
Преимущества ZooKeeper:
- Нет необходимости учитывать время истечения блокировки
- механизм наблюдения, если блокировка не удалась, вы можете наблюдать, чтобы дождаться освобождения блокировки, чтобы добиться оптимистичной блокировки
Но его недостатки:
- Не такой производительный, как Redis
- Высокие затраты на развертывание и эксплуатацию
- Клиент надолго теряет связь с Zookeeper, и блокировка снимается
Мое понимание распределенных блокировок
Что ж, распределенная блокировка, реализованная Redlock и Zookeeper на основе Redis, была подробно представлена, а также вопросы безопасности в различных нештатных ситуациях.Я хотел бы поговорить с вами о своих взглядах ниже.Это только для справки.
1) Вы хотите использовать Redlock или нет?
Ранее также было проанализировано, что Redlock может нормально работать только при условии «правильных часов», и если вы можете гарантировать это условие, то его можно использовать.
Но держать часы правильно, я не думаю, что это так просто, как вы думаете.
Во-первых, с аппаратной точки зрения, время от времени происходит перекос часов, и его нельзя избежать.
Например, температура ЦП, нагрузка на машину и материал микросхемы могут вызывать рассогласование тактовой частоты.
Во-вторых, из моего опыта работы., я столкнулся с ошибками часов и насильственной модификацией часов при эксплуатации и обслуживании, что, в свою очередь, влияет на правильность системы.Поэтому трудно полностью избежать человеческих ошибок.
Поэтому мое личное мнение о Redlock - стараться не использовать его как можно больше, да и производительность у него не такая хорошая, как у stand-alone версии Redis, да и стоимость развертывания тоже высока. Я все равно отдам приоритет использованию модель Redis «ведущий-ведомый + часовой» для реализации распределенных блокировок.
Как гарантировать, что правильность этого? Второй момент, чтобы дать вам ответ.
2) Как правильно использовать распределенные блокировки?
При анализе точки зрения Мартина он упомянул схему с токенами, которая меня очень вдохновила.Хотя эта схема имеет большие ограничения, это очень хорошая идея для сценариев, обеспечивающих «правильность».
Таким образом, мы можем объединить их для использования:
1. Используйте распределенные блокировки для достижения цели «взаимного исключения» на верхнем уровне.Хотя блокировка не будет работать в крайних случаях, она может в наибольшей степени блокировать одновременные запросы на верхнем уровне и снизить нагрузку на уровень операционных ресурсов. .
2. Но для бизнеса, который требует абсолютно корректных данных, ресурсный слой должен хорошо справляться с «покрытием итогов», а идею дизайна можно почерпнуть из схемы фейкового токена.
Объединив две идеи, я думаю, что для большинства бизнес-сценариев он уже может соответствовать требованиям.
Суммировать
Хорошо, подытожим.
В этой статье мы в основном обсуждаем вопрос о том, безопасны ли распределенные блокировки на основе Redis.
От реализации простейшей распределенной блокировки до обработки различных аномальных сценариев, введения Redlock и дебатов между двумя распределенными экспертами получаются применимые сценарии Redlock.
Наконец, мы также сравнили проблемы, с которыми Zookeeper может столкнуться при выполнении распределенных блокировок, и различия с Redis.
Здесь я резюмирую это содержание в виде ментальной карты для вашего удобства.
постскриптум
Объем информации в этой статье на самом деле очень большой, я думаю, что вопрос о блокировках распределения должен быть подробно раскрыт.
Если вы этого не понимаете, я предлагаю вам прочитать его еще несколько раз, построить в уме различные гипотетические сценарии и поразмышлять над ними.
При написании этой статьи я перечитал две статьи о дебатах двух великих богов о Редлоке.
1. В распределенной системной среде, казалось бы, идеальная схема проектирования может оказаться не такой уж «строго подогнанной», и если ее немного изучить, то можно обнаружить различные проблемы. Поэтому, думая о проблемах распределенных систем, мы должныБудьте осторожны снова.
2. Исходя из аргумента Рэдлока, мы не должны уделять слишком много внимания правильному и неправильному, но должны больше узнать о образе мышления Великого Бога и строгом духе критического изучения вопроса.
Наконец, давайте закончим тем, что написал Мартин после спора с Рэдлоком:
"Наши предшественники сделали для нас многое: стоя на плечах гигантов, мы можем создавать лучшее программное обеспечение. В любом случае, это часть процесса обучения, когда вы спорите и проверяете, выдерживают ли они критику со стороны других. Но целью должно быть приобретение знаний, а не убеждение других в своей правоте. Иногда это просто означает остановиться и подумать об этом."
взаимное поощрение.
Хотите увидеть больше хардкорных технических статей? Добро пожаловать, чтобы обратить внимание на мой публичный номер "капли воды и серебряные пули".
Я Кайто, старший back-end программист, который думает о технологиях, в своей статье я не только расскажу, что такое технический момент, но и расскажу, зачем вы им занимаетесь? Я также попытаюсь превратить эти мыслительные процессы в общую методологию, которую вы сможете применять в других областях, чтобы делать выводы друг из друга.
использованная литература: