Автор оригинала: David Wong.
Последнее изменение: 5 июня 2019 15:59:34
- Примечания по использованию redis + Lua-скрипта в PHP
- Официальный документ Redis использует redis для создания распределенных блокировок
- Взаимное исключение: в любое время только один клиент может получить блокировку
- Отсутствие взаимоблокировки: клиент выходит из строя во время удержания блокировки и активно не освобождает блокировку, что может гарантировать, что другие последующие клиенты получат блокировку
- Идентификатор владельца блокировки: блокировка и разблокировка должны быть одним и тем же клиентом, и клиент не может снять блокировку, не принадлежащую ему самому (у блокировки должен быть идентификатор).
Если это кластер redis, вам также следует учитывать Отказоустойчивость: Пока большинство узлов redis работают нормально, клиент может блокировать и разблокировать
Ниже рассматривается только сценарий автономного развертывания redis
Если это развертывание кластера redis, вы можете использовать
Пример блокировки PHP
$redis = new Redis();
$redis->pconnect("127.0.0.1", 6379);
$redis - > auth ("password"); // password verification
$redis - > select (1); // select the database to use. There are 16 databases by default
$key = "...";
$value = "...";
$expire = 3;
//Parameter explanation
//$value locked client request ID must be unique among all clients obtaining lock Qingqiu. The third condition above must be met: locked / unlocked is the same client
//"NX" only locks when the key does not exist, which meets the condition 1: mutually exclusive
//"Ex" sets the lock expiration time, meeting the condition 2: avoid deadlock
$redis->set($key, $value, ["NX", "EX" => $expire])Выполните приведенный выше результат кода:
- Блокировка, соответствующая ключу $, не существует. Запри ее
- Блокировка, соответствующая ключу$, уже существует, ничего не делайте
Точки, в которых легко ошибиться при блокировке:
Используйте
setnxиистечение срока действияСочетаниеПричина: если
setnxСбой пост-скрипта приведет к тупику
$стоимость Идентифицированный клиент:
- Просто используйте миллисекундную метку времени UNIX + идентификатор клиента (в основном достаточно)
- Используйте другие алгоритмы для обеспечения уникальных случайных значений генерируются
Подключение и подключение
В PHP, если вы используете pconnect При подключении к redis соединение, установленное с redis, будет сохраняться до конца жизненного цикла соответствующего процесса FPM после окончания текущего цикла объявления сценария. В то же время FPM повторно использует соединение при следующем запросе
То есть жизненный цикл этого соединения-это жизненный цикл процесса FPM, а не выполнение PHP-скрипта
Если используется код pconnect , закрыть Его функция заключается только в том, чтобы текущий PHP-скрипт больше не мог запрашивать redis, и он на самом деле не закрывает соединение с redis. Соединение по-прежнему будет повторно использоваться в последующих запросах
Функция Pconnect не может использоваться в версии потока
На рисунке выше показано, что соединение между PHP FPM и redis не отключается сразу после запрос заканчивается
Пример разблокировки PHP: использование скрипта Lua
$key = "...";
$identification = "...";
//Keys and argv are global variables in Lua scripts
$script = <<< EOF
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
EOF;
# $result = $redis->eval($script, [$key, $identification], 1);
//If the return result is > 0, the unlocking is successful
//The transfer order of parameters in PHP is not the same as the standard. Pay attention to the distinction
//The second parameter represents the incoming keys and argv, which are distinguished by the third parameter. Keys is in the front, argv is in the back
//The third parameter indicates the number of keys passed in
$result = $redis->evaluate($script, [$key, $identification], 1);Почему используются сценарии Lua:
Избегайте удаления блокировок, добавленных другими клиентами по ошибке
Напр.. если клиент слишком долго выполняет другие операции после получения блокировки, блокировка будет снята автоматически. В этом случае необходимо, чтобы клиент не удалял блокировку, полученную другими клиентами, для чего требуется идентификация блокировки
- Выполнить в скрипте Lua
получитьиделЭто атомарно. Весь сценарий Lua будет выполнен как команда - даже если
получитьСрок действия задней блокировки просто истекает, и в это время другие клиенты не будут заблокированы
Когда команда Eval выполняет код Lua, код Lua будет выполнен как команда, и redis не будет выполнять другие команды до тех пор, пока команда Eval не будет выполнена.
Из-за атомарности выполнения скрипта не выполняйте программы с чрезмерными накладными расходами в скрипте, иначе проверка повлияет на выполнение других запросов.
Разблокировка точек, подверженных ошибкам:
прямая
дельУдалить ключПричина: можно удалить блокировку, добавленную другими клиентами (в случае, если срок действия их блокировки истек)
получитьСудите о принадлежности замка и о том, соответствует ли он требованиямdelПричина: неатомная операция, если
получитьСрок действия почтового замка истек. В это время другие клиенты выполняют операцию блокировкидельБлокировка, добавленная другими клиентами, будет разблокирована по ошибке
↓ этот абзац воспроизводится из https://blog.csdn.net/zhouzme
Примечание:
- Редис принесет Все выполненные сценарии кэшируются в памяти
- Красный цвет в перезапуск При встрече Выпустить Ранее сохраненный сценарий
- Имена ключей и параметры, используемые в сценариях Lua, должны быть заменены ключами и argv. Никогда не пишите их в коде, если вы не абсолютно уверены, что они являются фиксированными и неизменными значениями каждый раз, когда вы их запрашиваете, особенно когда речь идет о времени и случайных числах. Вы должны заменить их параметрами, потому что Redis будет проверять, существует ли один и тот же сценарий в кэше сценариев каждый раз , когда он использует скрипт, в противном случае он будет храниться в кэше , если ваш сценарий очень длинный, и каждый запрос имеет разные значения переменных, будет создано бесчисленное множество кэшей сценариев, и вы обнаружите, что память redis увеличится. В начале, из-за слишком большого количества ключей и параметров, слишком сложно писать отдельно, поэтому легко и удобно объединять переменные в сценарий. В результате память продолжает расти, и это безумие-найти их, чтобы выяснить это, потребовалось много времени.
Семантические переменные должны быть Используйте локальные переменные То есть локальные , локальные переменные допустимы только в определенном блоке (структура управления, функция или фрагмент и т. Д.), Использование локальных переменных позволяет избежать конфликтов имен и ускорить доступ (локальные переменные и глобальные переменные в Lua хранятся по-разному)
Если сценарий Lua длинный и не является локальным или локальным, рекомендуется использовать подпись SHA Это может сэкономить пропускную способность, но, похоже, не улучшает производительность напрямую. Принцип, который я понимаю, когда здесь популяризируется Xiaobai, заключается в том, что redis создаст уникальную подпись для каждого сценария, примет сценарий в качестве тела функции и использует подпись в качестве имени функции сценария, чтобы поместить его в кэш, поэтому для последующих вызовов нужно только передать подпись Sha для вызова функции, что намного проще. Подписи, созданные одним и тем же сценарием, одинаковы, поэтому подпись Sha может быть сначала сгенерирована локально, а затем один раз загружена сценарием на сервер. Только подпись должна быть сохранена и использована в программе. Кроме того, следует отметить, что при изменении сценария даже новая строка или пробел (которые легко игнорировать или неправильно использовать) должны быть перезагружены для получения новых общих ресурсов
Примечание: получение подписи Sha-это отдельная функция. Не включайте это в свой обычный процесс. При локальной разработке Sha может быть сгенерирован, а строка записана в процессе. В одном и том же сценарии reids всегда генерирует одну и ту же подпись.
- Внесено Eval Параметр argv преобразуется в строку, если он изначально был числовым Если вам нужно оценить числовое суждение, такое как переменная > 0 или < 0 в вашей логике, вы должны преобразовать строку в число
tonumber()Способесли (tonumber(ARGV[1]) > 0), то возвращает 1; конец; - Я протестировал несколько сценариев Lua и сравнил их с конвейером, и обнаружил, что эффективность скрипта, как правило, на 30-40% выше, чем у конвейера
По сравнению с одной машиной, кластер redis должен учитывать одну Отказоустойчивость , более сложную конструкцию
Поскольку я никогда не практиковал это, я сначала опубликовал официальный учебник, который был довольно шокирующим
Поскольку я никогда не практиковал это, я сначала опубликовал официальный учебник, который был довольно шокирующим
Соответствующий перевод: http://ifeve.com/redis-lock/
Алгоритм красной блокировки
Чиновник дает алгоритм красной блокировки
Сценарий: В настоящее время существуют полностью независимые главные узлы redis, развернутые на разных хостах
Операция блокировки приобретения клиента:
- Используйте один и тот же ключ и уникальное значение (как значение) для запроса блокировок с этих n узлов redis одновременно. Время ожидания блокировок должно быть > > время ожидания (учитывая, что запрос занимает много времени). Если узел заблокирован, его следует пропустить как можно скорее
- Рассчитайте время, затраченное на шаге 1. Если общее затраченное время превысит тайм-аут, блокировка будет считаться неудачной. Клиенту необходимо успешно получить блокировку на большинстве (более чем половине) узлов, чтобы считаться успешным
- Если блокировка выполнена успешно, время действия блокировки равно первоначальному времени действия блокировки – время, затраченное на шаг 1
- В случае сбоя блокировки (тайм-аут или неспособность получить блокировку более чем в половине экземпляров N/2 + 1) клиент снимет блокировку с каждого узла (каждого, даже если узел ранее думал, что блокировка не удалась).
Оригинал: “https://developpaper.com/php-uses-redis-to-implement-distributed-locks/”