У меня есть таблица MySQL с электронными письмами, которые должны быть отправлены.
При каждой загрузке страницы я проверяю наличие неотправленных писем, беру несколько из них и отправляю их.
Чтобы две одновременные загрузки страниц не отправляли одно и то же электронное письмо, я думаю сделать что-то вроде этого:
$pdo = new PDO(...);
// Start blocking other page loads
$pdo->beginTransaction();
$stmt = $pdo->query("SELECT id, recipient, subject, body
FROM emails WHERE sent = 0 LIMIT 1 FOR UPDATE");
$mail = $stmt->fetch();
if(false !== $mail)
$pdo->exec("UPDATE emails SET sent = 1 WHERE id = $mail['id']");
// End blocking other page loads
$pdo->commit();
if(false !== $mail) {
// Send e-mail
}
Но что, если выполнение будет прервано после фиксации, но до того, как электронное письмо будет успешно отправлено? Письмо будет отправлено на рынок, но на самом деле отправлено не будет. Конечно, я могу подождать с фиксацией до того, как письмо будет отправлено, но это приведет к гораздо более длительному периоду блокировки. Я отправляю электронные письма по SMTP, а отправка одного письма занимает около 10 секунд.
У вас есть идеи, как это решить? Одним из вариантов может быть обнаружение блокировки таблицы, а затем просто пропустить весь этот шаг. Это возможно?
Используйте для этого систему очередей (redis, beanstalkd, RabbitMQ и т. Д.), Если вы хотите, чтобы это каким-либо образом масштабировалось.
Отправка электронных писем на основе загрузки страниц — ужасная идея в долгосрочной перспективе, так как вы не отправляете электронные письма асинхронно, но замедляет загрузку страницы случайных пользователей на много.
Вот пример:
Возьмите очередь redis и опубликуйте строку json, включая идентификаторы электронной почты, которые будут отправлены с них:
{"id":1, "job":"pending", "data": {"user": "foobar"}}
Сделайте cronjob, чтобы подписаться на эту очередь, подключиться к базе данных и отправлять электронные письма с этими идентификаторами.
Если есть ошибка, вы просто меняете работу на "job":"errored"
, При следующем запланированном запуске задачи электронной почты вы справитесь с ней там.
Существует довольно много библиотек очередей, и выполнение асинхронных задач при загрузке страницы — неправильный способ сделать это.
Откат — это проблема
pending
,successfull
,failed
могут быть возможные значения для status
поле.
Вы должны проверить ошибки доставки электронной почты, чтобы отслеживать фактическую доставку электронной почты (например, 10 из 100 отправленных), попробуйте поработать с Maildir
и проверить наличие новых писем по таким ключевым словам failure
+[email protected]
по электронной почте, затем обновите базу данных соответственно.
Работа с большим количеством писем для отправки.
использовать очереди, вероятно, самые безопасные с порогом и «перезарядкой» после того, как группа писем была отправлена.
создать файл блокировки, чтобы избежать гонки, если он существует, тогда иди спать, а затем начать отправлять
Вы могли бы отметить это как в ожидании прежде чем отправить его и как Отправить после того как вы отправили его. Но вам все еще нужно подумать о ситуации, когда что-то происходит между установкой в ожидании и установив его Отправить.
Если вы не хотите отправлять больше, чем «x» электронных писем параллельно, то вы можете подумать над подсчетом записей, помеченных как в ожидании прежде чем отправлять почту.
Что-то вроде того:
$pdo = new PDO(...);
// Start blocking other page loads
$pdo->beginTransaction();
$stmt = $pdo->query("SELECT id, recipient, subject, body
FROM emails WHERE status = 'queued' LIMIT 1 FOR UPDATE");
$mail = $stmt->fetch();
if(false !== $mail)
$pdo->exec("UPDATE emails SET status = 'pending' WHERE id = $mail['id']");
// End blocking other page loads
$pdo->commit();
if(false !== $mail) {
// Send e-mail
if( $successfull ) {
$pdo->exec("UPDATE emails SET status = 'sended' WHERE id = $mail['id']");
} else {
$pdo->exec("UPDATE emails SET status = 'failed' WHERE id = $mail['id']");
}
}