Недавно в рамках учебного задания, организованного школой, нашей группе необходимо завершить набор услуг распределенной и микро-трансграничной электронной коммерции. Хотя эта тема кажется немного старомодной, и большинство наших товарищей по команде-это стеки технологий Java, поэтому я польщен (вынужден) Мы использовали свул PHP, чтобы помочь студентам, отвечающим за серверную часть, написать несколько модулей микросервиса. В сотрудничестве между членами команды все еще есть много интересных искр.
Во время вчерашнего обзора кода товарища по команде мы обнаружили, что, похоже, существует проблема с методом написания распределенной блокировки нашей группы. Код реализации выглядит следующим образом:
Запорная часть
Отпирающая часть
Основной принцип заключается в использовании setnx redis для вставки набора значений ключа, в котором идентификатор ключа, подлежащего блокировке (в проекте идентификатор пользователя заблокированного пользователя), возвращает значение FALSE в случае сбоя блокировки. Но согласно идее двухступенчатого замка, после тщательного рассмотрения возникает такое интересное явление:
Если запрос микросервиса а блокирует пользователя, запрос микросервиса а может считывать информацию пользователя и изменять ее содержимое; другие модули могут считывать только информацию пользователя и не могут изменять ее содержимое. Предполагая, что текущий запрос микросервиса a разблокирует пользователя, все модули могут считывать информацию пользователя и изменять ее содержимое таким образом:
- Если модуль микросервиса a получает запрос от другого пользователя, которому необходимо внести изменения, при условии, что пользователь все еще заблокирован, может ли запрос изменить его? (да, просто разблокируйте)
- Если модуль микросервиса B получает запрос от другого пользователя, которому необходимо внести изменения, при условии, что пользователь все еще заблокирован, может ли запрос изменить его? (да, просто разблокируйте)
- Если модуль микросервиса a случайно выйдет из строя во время запроса на блокировку, могут ли другие пользователи изменить информацию? (да, просто разблокируйте)
Очевидно, что эти три пункта-не то, чего мы хотим. Итак, как лучше всего реализовать распределенную блокировку?
Чего должна достичь хорошая распределенная блокировка
- Блокировка по определенному запросу модуля и разблокировка только по этому запросу этого модуля (взаимоисключающие, только один запрос микросервиса может удерживать блокировку)
- Если запрос на блокировку модуля блокировки выполняется сверхурочно, он должен быть автоматически разблокирован и его модификация должна быть восстановлена (отказоустойчивость, даже если микросервис, удерживающий блокировку, отключен, это, наконец, не повлияет на блокировку других модулей).
Что мы должны сделать, чтобы подвести итог: в случае реализации взаимного исключения модулей в распределенных блокировках нашей группы игнорируется важная проблема – “запросить взаимное исключение”. Нам нужно только сохранить значение значения ключа в качестве запрошенного текущего запроса при блокировке и добавить несколько суждений при разблокировке, чтобы определить, является ли это одним и тем же запросом.
Итак, после этой модификации мы можем быть спокойны?
Да, этого достаточно. Поскольку среда разработки redis является единственным примером на одном сервере, нет проблем с распределенной блокировкой, реализованной вышеуказанным способом, но при подготовке к развертыванию в производственной среде мы внезапно осознали проблему: если реализовано разделение чтения и записи между ведущим и ведомым устройствами, при реализации синхронных данных redis с несколькими ведущими и ведомыми устройствами используется асинхронная репликация, которая является операцией “записи” в наш re После основной базы данных IDS, она немедленно вернется к успеху (она не будет ждать, пока она не будет синхронизирована с подчиненной базой данных, если он синхронизирован, а затем возвращен, это синхронная репликация). Это вызовет проблему:
Если запрос с модулем a успешно заблокирован, а основная база данных перед подчиненной базой данных не синхронизирована, мы сыграем плохо (отключено), затем часовой redis выберет новую главную базу данных из подчиненной базы данных. В это время, если запрос в модуле a снова заблокирован, он будет выполнен успешно.
Мы можем рисовать воду (ТУМАН) только с помощью поисковой системы. Мы находим, что есть общее решение: красная блокировка.
Как реализовать распределенную блокировку безопасности redlock
Прежде всего, redlock-это метод реализации, рекомендованный официальным документом redis. Он не использует архитектуру “ведущий-ведомый”. Он использует главную базу данных с несколькими состояниями для получения блокировок по очереди. Предположим, что существует 5 основных складов, и общий процесс выглядит следующим образом:
Запирать
- Запросы прикладного уровня на блокировку
- Отправляйте запросы по очереди на пять серверов redis
- Если более половины серверов успешно вернутся к блокировке, блокировка будет завершена. Если нет, разблокировка будет выполнена автоматически и повторена после ожидания в течение случайного периода времени. (объективная причина сбоя блокировки: состояние сети плохое, сервер не отвечает и другие проблемы, подождите случайный период времени и повторите попытку, чтобы избежать ситуации “роения”, в результате чего занятость ресурсов сервера быстро увеличивается)
- Если какой-либо из серверов уже заблокирован, блокировка не выполняется. Подождите случайный промежуток времени и повторите попытку. (не удалось заблокировать по субъективным причинам: заблокирован кем-то другим)
Разблокировка
Вы можете отправить запрос непосредственно на 5 серверов, независимо от того, есть ли блокировка на этом сервере или нет. Общая идея очень проста, но есть еще много достойных внимания мест, которые нужно реализовать. При отправке запроса на блокировку на эти пять серверов, поскольку время истечения срока действия будет указано для обеспечения “автоматической разблокировки (отказоустойчивости)”, упомянутой выше, учитывая задержку и другие причины, время автоматической разблокировки этих пяти серверов не совсем совпадает, поэтому существует проблема разницы во времени блокировки, которая обычно решается следующим образом:
- Перед блокировкой необходимо записать временную метку T1 запроса на блокировку на уровне приложения (или инкапсулировать распределенную блокировку как глобальную микросервису).
- После блокировки последней основной базы данных redis запишите метку времени T2
- Время, необходимое для блокировки, составляет T1 – T2
- Предполагая, что время автоматической разблокировки ресурсов составляет 10 секунд, время реального использования ресурсов равно 10 – T1 + T2. если
Доступное время не соответствует ожиданиям, или это отрицательное число. Знаешь, давай сделаем это снова. Если у вас есть более строгий контроль над временем истечения срока действия блокировки, вы можете записать время от T1 до первого сервера, когда блокировка будет успешно добавлена, а затем добавить это время к последнему доступному времени, чтобы получить более точное значение.Теперь рассмотрим другую проблему. Если блокировка определенного запроса существует на трех серверах, и все три из них не работают (как не повезло… ТАТ), затем приходит еще один запрос на блокировку. Разве это не возвращение к проблеме, с которой наша команда столкнулась в самом начале? К сожалению, да, официальный документ дает ответ на этот вопрос: включение настойчивости улучшит
Что касается обработки производительности, то, вообще говоря, она требует не только низкой задержки, но и высокой пропускной способности. Согласно официальным документам, мы можем использовать мультиплексную передачу для одновременной связи с пятью основными библиотеками redis, чтобы сократить общие затраты времени, или установить сокет в неблокирующий режим (преимущество в том, что при отправке команды он не ждет возврата, поэтому мы можем отправить все сразу. Я буду снова ждать общего результата работы, хотя я думаю, что в целом, если задержка в сети очень мала, время ожидания займет большую долю.)
Если у вас есть какие-либо вопросы, пожалуйста, зайдите в мой блог: http://www.zzfly.net/redis-re