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

问题描述:

我有一个带有电子邮件的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秒。

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

+2

你是否在页面加载中检查了这个,因为你不能在你的webspace上使用cronjobs? –

你可以把它标记为未决在发送前和发送后,你必须把它。但是您仍然需要考虑在将其设置为待定并将其设置为发送之间发生的情况。

如果您不想发送更多“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']"); 
    } 
} 

回滚是一个问题

pendingsuccessfullfailed可以为status领域是可能的值。

你必须检查电子邮件传送失败,所以你跟踪你实际交付的邮件(例如:10提供了100 sended),尝试用Maildir工作并检查新邮件的关键字,那么这样failure + [email protected]电子邮件更新数据库相应。

使用大量的电子邮件发送。

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

  • 创建一个锁文件,以避免种族,如果它存在,然后去睡觉别的开始发送

使用排队系统本(Redis的,beanstalkd,RabbitMQ的,等等),如果你想要这个以任何方式进行缩放。

发送基于页面加载邮件是从长远来看,一个可怕的想法,因为你不发送电子邮件异步,而是由很多放缓随机用户的页面加载下来。

下面是一个例子:

乘坐Redis的队列,并发布JSON字符串包括电子邮件ID从他们发送:

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

做一个cronjob订阅这个队列,并连接到数据库并发送这些ID的电子邮件。

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

这里有很多的队列库,并且在页面加载上执行异步任务是错误的方式。