Я предоставляю услугу, при которой мои клиенты могут автоматически оплачивать продление своих кредитов, когда их учетная запись достигает определенного порога. Это работает так же, как мандрил.
Уровень кода работает следующим образом: когда пользователь делает запрос, приложение уменьшает его кредиты до одного, а затем проверяет, текущие кредиты == {сумма автоматического продления}. В этом случае задание отправляется в службу очереди, чтобы начать обновление.
Тест здесь критичен, я проверяю, остается ли значение EQUALS для автоматического обновления, а не когда оно НИЖЕ ИЛИ EQUAL, потому что в этом случае обновление будет выполняться каждый раз, когда пользователь запрашивает API, в то время как первое автоматическое обновление имеет не был полностью обработан.
Но у меня была странная проблема сегодня. У одного из моих клиентов было два продления вместо одного. Мое первое предположение состоит в том, что в API есть условие гонки, означающее, что два запроса выполняются одновременно, при этом «оставшиеся» кредиты идентичны и равны числу для автоматического продления, что приводит к двум автоматическим продлениям.
Мой вопрос здесь довольно прост: как я могу избежать двух обновлений?
Основная идея состоит в том, чтобы исправить проблему состояния гонки, но я не понимаю, как API написан на PHP с использованием NGinx. Сервер должен знать, что к одной и той же учетной записи сделано N запросов, чтобы рассчитать текущее использование кредитов (оставшихся — {одновременных запросов}), что кажется невозможным.
Как я могу это сделать?
Решение состоит в том, чтобы позволить только одному процессу PHP запускать функцию автозарядки в любой момент времени. Самый простой способ сделать это — окружить ваш код flock()/fclose()
:
$lock = fopen("/tmp/renew", "w+");
flock($lock, LOCK_EX);
<renew code>
fclose($lock);
Это гарантирует, что будет запущен только 1 процесс PHP <renew code>
вовремя. Таким образом, даже если поступит 100 запросов на обновление, запрос № 1 запустит код обновления, а запросы № 2- # 100 прекратят работу при нажатии flock()
, Когда # 1 заканчивается, # 2 запускается, когда # 2 заканчивается, # 3 запускается и так далее.
Я, наконец, пришел с этим решением:
Поскольку сценарий возобновления вызывается из системы очередей (Beanstalk), а мой сервер запускает только один экземпляр этого сценария возобновления, я снова проверяю в сценарии возобновления необходимость продления кредитов.
Предположим, есть два вызова для одной и той же учетной записи, первый вернет меньше кредитов, чем количество auto_renew, добавив, таким образом, новые кредиты.
Второй запуск покажет, что теперь больше кредитов, чем количество auto_renew, и будет проигнорировано.
Я думаю, что закрыл проблему, но я предпочитаю дать понять, что у @Brian with был умный способ сделать это в случае, если скрипт не вызывается из системы очередей !.