避免竞争条件,但仍能够回滚

时间:2015-10-07 09:22:44

标签: php mysql email atomic race-condition

我有一个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秒钟。

您对如何解决这个问题有什么想法吗?一个选项可能是检测表是否已锁定,然后只是跳过整个步骤。这可能吗?

3 个答案:

答案 0 :(得分:2)

如果您希望以任何方式进行扩展,请使用排队系统(redis,beanstalkd,RabbitMQ等)。

从长远来看,基于页面加载发送电子邮件是一个可怕的想法,因为您不是异步发送电子邮件 ,而是通过减慢随机用户的页面加载速度很多

以下是一个例子:

获取redis队列,并发布包含要从其发送的电子邮件ID的json字符串:

{"id":1, "job":"pending", "data": {"user": "foobar"}}

创建一个cronjob来订阅此队列,并连接到数据库并发送带有这些ID的电子邮件。

如果出现错误,您只需将作业更改为"job":"errored"即可。在下一次计划的电子邮件任务运行中,您可以在那里处理它。

那里有相当多的队列库,并且在页面加载上执行异步任务是错误的方法。

答案 1 :(得分:0)

您可以在发送之前将其标记为待处理,并在发送之后将其标记为发送。但您仍需要考虑将其设置为挂起并将其设置为发送之间发生某些事情的情况。

如果您不想发送更多信息,那么' 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']");
    }
}

答案 2 :(得分:0)

回滚是一个问题

pendingsuccessfullfailed可能是status字段的可能值。

您必须检查电子邮件递送失败,以便跟踪您实际发送的电子邮件(例如,100个已发送的邮件),尝试使用Maildir并检查关键字{{1}的新邮件+} + failure电子邮件然后相应地更新数据库。

使用大量电子邮件发送。

  • 利用队列,在发送一组电子邮件后可能是最安全的阈值和“冷却时间”。

  • 创建一个锁定文件以避免竞争,如果存在则继续睡觉,否则开始发送