У меня есть мобильное приложение и сервер на основе Symfony, который предоставляет API для мобильного приложения.
У меня есть ситуация, когда пользователям могут понравиться Post
, Когда пользователям нравится Post
Я добавляю запись в таблицу ManyToMany, что этот конкретный пользователь понравился именно этому Post
(шаг 1). Затем в Post
Таблицу я увеличиваю LikeCounter (шаг 2). Затем в User
Таблица увеличения баллов геймификации для пользователя (потому что ему понравилось Post
) (шаг 3).
Таким образом, существует ситуация, когда многим пользователям нравится Post
в то же время и тупик происходит (на Post
стол или на User
Таблица).
Как справиться с этим? В Учение Документы Я вижу решение, как это:
<?php
try {
// process stuff
} catch (\Doctrine\DBAL\Exception\RetryableException $e) {
// retry the processing
}
но что мне делать в catch
часть? Повторите весь процесс симпатии (шаги с 1 по 3), например, 3 раза, а в случае неудачи верните BadRequest в мобильное приложение? Или что-то другое?
Я не знаю, является ли это хорошим примером, потому что, возможно, я мог бы попытаться перестроить процесс, чтобы тупик не возник, но я хотел бы знать, что мне делать, если они действительно произойдут?
Что бы я сделал, это разместил бы все лайки в очереди и использовал их, используя серийный потребитель так что вы можете сгруппировать обновления по одному посту.
Если вы настаиваете на сохранении своей текущей реализации, вы можете пойти по пути, который вы сами предложили, вот так:
<?php
for ($i = 0; $i < $retryCount; $i++) {
try {
// try updating
break;
} catch (\Doctrine\DBAL\Exception\RetryableException $e) {
// you could also add a delay here
continue;
}
}
if ($i === $retryCount) {
// throw BadRequest
}
Проблема в том, что после сбоя Symfony Entity Manager — он закрывает соединение с БД, и вы не можете продолжать работу с БД, даже если перехватывает исключение ORMException.
Первое хорошее решение — это обработать асинхронные «лайки» с помощью rabbitmq или другой реализации очереди.
Шаг за шагом:
{type: 'like', user:123, post: 456}
У вас может быть несколько потребителей, которые пытаются получить блокировку на основе postId. Если два потребителя попытаются обновить один и тот же пост — один из них не получит блокировку. Но все в порядке, вы можете получить сообщение об ошибке после.
Второе решение — иметь специальную таблицу, например, post_likes (userId, postId, timestamp). Ваша конечная точка может создавать новые строки в этой таблице синхронно. И вы можете посчитать «лайки» в каком-то посте с этой таблицей. Или вы можете написать некоторый скрипт cron, который будет обновлять количество лайков постов по этой таблице.
Я не согласен со Стефаном, взаимоблокировки нормальны, как говорится в документации MySQL:
Обычно вы должны писать свои приложения так, чтобы они всегда были готовы повторно выполнить транзакцию, если она откатывается из-за тупика.
Тем не менее, цикл, предложенный Стефаном, является правильным решением. За исключением того, что в нем отсутствует важный момент: после того, как Doctrine выдает исключение, EntityManager становится непригодным для использования, и вы должны создать новый в предложении catch с помощью resetManager () из экземпляра ManagerRegistry.
Когда у меня было точно такое же беспокойство, как и у вас, я искал в Интернете, но не смог найти полностью удовлетворительного ответа. Поэтому я испачкал руки и вернулся со статьей, где вы найдете пример реализации того, что я сказал выше: