cronjob 是使用 SMTP 通过 PHPMailer 发送大量通知电子邮件的正确方法吗?

use*_*303 4 php mysql email cron phpmailer

我有一个 Web 应用程序 (PHP/MYSQL),每次有人通过我的应用程序中的表单在我的数据库中提交一个条目时,它都会向与该条目关联并选择接收通知的每个人发送一封通知电子邮件。

例如,有一个足球采摘池,里面有 15 个人。当第 16 个人提交他们的条目时,0-15 人将收到通知,告知有新条目。可能是 1,可能是全部 15,可能是其中的一半。

但我的网站可能有 100 个足球池,有些有 5 人,有些有 100 人。

因此,我设置了一个单独的表,记录有关每个条目的基本信息,并将“已发送”列设置为“0”。

然后我让下面的 cronjob 每隔 MINUTE 运行一次,它会查找所有带有“0”的条目(这意味着与该条目关联的人尚未收到通知)并循环遍历并向每个关联的人发送唯一的电子邮件。我发送独特的电子邮件,因为每封电子邮件都有一个取消订阅链接和特定于该人的信息。发送电子邮件后,它将所有这些条目“已发送”列更新为“1”,因此它将被忽略。

我通过 Amazon SES 使用 PHPMailer SMTP 发送电子邮件。

<?php
require_once("includes/session.php");
require_once("includes/connection.php");
require_once("includes/functions.php");
require 'phpmailer/PHPMailerAutoload.php';

//find all new entries with sent = 0, loop thru and send emails
$stmt = $pdo->prepare("SELECT * FROM cron_email_notify WHERE sent = 0");
$stmt->execute();
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    //bunch of variables set here (which i'll leave out for brevity's sake) to be used in the emails (like firstname, etc)

        //Next, send a unique email to all people who have chosen to be notified
        $stmt = $pdo->prepare("SELECT * FROM entries WHERE poolid = ? AND notify = 'yes'");
        $stmt->execute([$poolid]);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            //get emails into array
            $notifyemailsarray[$row['email']]=$row;
        }

        /// Send notification email to all people who wish to be notified, if any
        $mail = new PHPMailer;
        //$mail->SMTPDebug = 3;                               // Enable verbose debug output
        $mail->isSMTP();                                      // Set mailer to use SMTP
        $mail->SMTPKeepAlive = true;  //Helps with speed for multiple emails
        $mail->Host = 'tls://email-smtp.us-east-1.amazonaws.com';  // Specify main and backup SMTP servers
        $mail->SMTPAuth = true;                               // Enable SMTP authentication
        // and so on with other phpmailer info which I cut out for brevity's sake

        if (!empty($notifyemailsarray)) {
            foreach($notifyemailsarray as $email => $details)   {
                // Assemble the fullname here
               $fullname = $details['firstname'] . ' ' . $details['lastname'];
               $mail->addAddress($details['email'], $fullname);
               $mail->Body    =  "(leaving out for brevity's sake)";
               $mail->AltBody = "(leaving out for brevity's sake)"; 
                if(!$mail->send()) {
                    echo 'Message could not be sent.';
                    echo 'Mailer Error: ' . $mail->ErrorInfo;
                } else {
                    //email sent
                }
                // Clear all addresses for next loop
                $mail->clearAddresses();
            }
        } else {
            //no people  are set to be notified 
        }
        $mail->SmtpClose();  
    //update cron_email_notify table's sent field to 1, so we know not to send again
    $stmt = $pdo->prepare("UPDATE cron_email_notify SET sent = 1 WHERE poolid = ? and sent = 0");
    $stmt->execute([$poolid]);
}
?>
Run Code Online (Sandbox Code Playgroud)

在测试中一切正常,但我只测试了它发送 10 封电子邮件的地方(即小土豆)

我的应用程序将收到大量提交,并且应该会随着时间的推移而不断增长,所以我只是想知道这是否可能是一个潜在的问题(我在后台运行 cronjobs 的经验很少,所以不确定有多疯狂操作/流量必须会影响站点的性能)。

我很担心,因为上面的 cronjob 脚本在发送 10-15 封电子邮件时似乎需要大约 5 秒钟才能完成。

现在,假设有大量条目涌入后,我的实时站点上启动了一个 cronjob,它必须发送 200 封电子邮件。如果 cronjob 需要超过一分钟的时间运行,下一个 cronjob 将开始(因为它们每分钟运行一次)并且这不能完全使我的系统陷入困境吗?或者同一个 cronjob 可以在另一个 cronjob 运行时正常运行吗?

显然,许多网站发送了大量通知电子邮件,因此这必须是一个常见的需求,我确信有一个很好的解决方案,所以只是好奇任何人的想法/建议/等。也许我这样做的方式很好,直到我获得如此多的流量,这将是一个大问题。或者也许我现在可以做更多的事情来帮助抵消可能陷入困境的系统?

Bil*_*win 6

我曾在一家向美国一半以上的公立学校发送通知的公司工作。我们每天发送 1000 万封电子邮件。

您的代码是单线程的,因此它将一个接一个地串行发送电子邮件,这可能需要一段时间。你问如果它花费的时间超过你的 cron 作业之间的间隔会发生什么?也就是说,如果您的 cron 作业每分钟运行一次,但在某个时候它会在前一个脚本仍在运行时启动您的脚本。然后您的脚本的两次调用都将处理同一批电子邮件,您的用户将开始收到重复的电子邮件。

出于这个原因,运行守护进程可能更好。这只是意味着不是每分钟 cron 启动您的 PHP 脚本,而是您编写 PHP 脚本以运行while (true)循环并且永不退出。在循环结束时,编写脚本sleep()最多 60 秒。然后它转到循环的顶部,检查数据库以查看是否有一批电子邮件要运行,并处理该批邮件。然后它返回休眠状态,持续的秒数等于 60 减去处理批次所需的时间(但不少于零秒)。

这样,如果出现激增,它永远不会同时运行两个应用程序,即使一批电子邮件需要 75 秒。您的守护进程的睡眠计算将使其睡眠最少 0 秒,然后它会立即开始下一批。希望激增是暂时的,应用程序可以在某个时候赶上。否则,您的应用程序将越来越落后于计划。

您的应用程序应该将它发送的电子邮件数量以及批次之间休眠的时间写到日志文件中。您应该监视此文件,以便如果它无法跟上需求,您就会知道。一旦你变得非常专业,你就会把这些数据放到 Grafana 仪表板或类似的东西中。

如果您的流量增长到单线程应用程序始终无法在 60 秒内发送一批电子邮件的程度,那么您将不得不并行运行多个电子邮件发件人应用程序。PHP 在多线程编程方面很糟糕,因此您可能想要使用 Java 或 Go。

尝试让多个线程轮询数据库是一个坏主意,因为您会遇到竞争条件和锁争用。我们在我公司所做的是让一个线程轮询数据库。当它找到一批要发送的电子邮件时,它会将它们推送到消息队列中(我们使用的是 ActiveMQ,但还有其他的)。消息队列使多个线程可以轻松地从队列中取出项目。然后,多个并发电子邮件发送应用程序线程很容易并行处理批量电子邮件。

PS:您是否意识到sent=1即使部分或全部电子邮件未能发送,您的代码也会标记池?