PHP / Mysql проблема с одновременными вызовами БД - PullRequest
3 голосов
/ 05 марта 2019

Использование таблиц innoDB и mysqli-оболочки в PHP для запросов.

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

Ситуация такова, что первое количество пользователей Х, получивших доступ к сценарию, получает приз.

Приз - это отдельная запись в таблице «призов», в которой указано количество # заявленных и # выделенных.

Как только использованная сумма> = выделенная сумма, мы прекращаем присуждать призы.

Происходит то, что несколько запросов к сценарию ПРОЧИТАЮТ строку одновременно, прежде чем другие экземпляры сценария смогут ОБНОВИТЬ строку, таким образом показывая каждому из них, что осталось еще # призов, которые могут быть запрошены. Это заставляет нас выплачивать больше, чем выделенная сумма.

Есть идеи, как это обойти?

Ответы [ 3 ]

1 голос
/ 05 марта 2019

Хотя вы не предоставляете код, структуры таблиц или более подробную информацию о базе данных, первым советом в этих случаях будет использование LOCK

Если ваша таблица innoDB, вы можете воспользоватьсяБлокировка на уровне строк, хотя это не имеет значения, если таблица имеет одну строку.

В псевдокоде при каждом попадании вам потребуется:

LOCK TABLE prizes
SELECT claimed, alloted FROM prizes

if claimed < alloted award prize
UPDATE prizes set claimed = claimed +1

else
do_nothing
UNLOCK TABLE prizes

<<after unlocking>>
if the user got an award, do whatever you need to do to award the prize which is not "inventory-sensitive" and can be done asynchronously

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

Это может быть сложно, сложно, привередливо ...

Более простой путь:

создать таблицу claim_attempt с автоматическим-инкрементный первичный ключ и поле, которое ссылается на что-то о пользователе.

При каждом попадании вставляйте запись (независимо от доступного инвентаря) и извлекайте идентификатор вставленной строки.После этого сравните полученный идентификатор с назначенным номером награды.Если id <= alloted, то запустите любой процесс, который необходимо запустить, чтобы вручить приз пользователю.Если id> выделен, выведите сообщение «повторить попытку в следующий раз»

1 голос
/ 05 марта 2019

Правильно, вы описываете классическое состояние гонки.

Одним из решений является использование SELECT...FOR UPDATE для установки блокировки строки в таблице призов перед ее обновлением. InnoDB устанавливает порядок запросов на блокировку, заставляя каждый запрос ждать своей очереди, пока не сможет получить блокировку.

Однако это не очень хорошее решение, потому что оно заставит браузер каждого пользователя крутиться и крутиться, ожидая ответа. На сервере вы быстро получите 1500 запросов на блокировку в секунду. Даже если для каждой сессии требуется всего 10 миллисекунд, чтобы выполнить SELECT FOR UPDATE и последующие UPDATE (это уже довольно амбициозно), это все равно 15,0 секунд работы, которую нужно выполнять каждую секунду. Ко второй секунде у вас будет 30,0 секунд работы.

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

По сути, вам нужно какое-то решение для определения порядка запросов:

  • Global
  • Атомный
  • Быстрая
  • Асинхронный

Вы можете сделать так, чтобы каждый параллельный запрос выполнял INSERT для таблицы с ключом AUTO_INCREMENT, который будет гарантировать их порядок. Затем, когда есть X строк, последующие запросы не удосуживаются вставить больше строк.

Другой способ - использовать очередь сообщений. Каждый запрос просто помещает свой собственный запрос в очередь. Затем один потребитель извлекает первые X запросов из очереди и вручает им призы. Остальные запросы в очереди сбрасываются, и они не получают приз.

0 голосов
/ 05 марта 2019

Одним из способов решения этой проблемы является создание таблицы, содержащей отдельные записи о призах, и попытка «потребовать» одну из них с помощью запроса, подобного:

UPDATE prizes SET claimed_by=? WHERE prize_type=? AND claimed_by IS NULL LIMIT 1

Если вы наложите ограничение UNIQUE на prize_type и claimed_by, то это означает, что никто не может претендовать более чем на один приз данного типа. Это применяется на уровне базы данных и не может быть обойдено из-за проблем с синхронизацией.

Когда вы вызываете это обновление, вы модифицируете либо ноль, либо одну строку. Проверьте количество обновленных строк результата, чтобы увидеть, было ли утверждение успешным.

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