如何确保在任务队列使用情况下不发送两次电子邮件?

时间:2014-05-30 11:50:34

标签: google-app-engine email cron task-queue

我想更改我的GAE应用逻辑并开始使用任务队列发送电子邮件。

目前我有一个cron作业,每15分钟运行一次并读取要从数据存储区发送的消息:

class SendMessagesHandler(webapp2.RequestHandler):
    def get(self):
        emails_quota_exceeded = models.get_system_value('emails_quota_exceeded')
        if emails_quota_exceeded == 0 or emails_quota_exceeded == None:
            messages = models.get_emails_queue()
            for message in messages:
                try:
                    ...
                    email.send()
                    models.update_email_status(message.key.id()) # update email status indicating that the mail has been sent 
                except apiproxy_errors.OverQuotaError, error_message:
                    models.set_system_value(what='emails_quota_exceeded', val=1)
                    logging.warning('E-mails quota exceeded for today: %s' % error_message)
                    break
        else:
            logging.info('Free quota to send e-mails is exceeded')

如果我使用任务队列,那么我会得到类似的东西:

for message in messages:
    taskqueue.add(url='/sendmsg', payload=message)

在这种情况下,相同的消息可能会被发送两次(甚至更多次) - 例如,如果它尚未发送,但是第二次执行了cron作业。 如果我在将消息添加到队列后立即更新电子邮件状态:

for message in messages:
    taskqueue.add(url='/sendmsg', payload=message)
    models.update_email_status(message.key.id()) # update email status indicating that the mail has been sent 

然后可能永远不会发送消息。例如,如果在发送电子邮件期间发生异常。了解该任务将被重试,但如果今天超过配额,则重试将无济于事。

我想我也可以在尝试发送之前重新读取任务队列中每条消息的状态,但这会花费额外的读取操作。

处理它的最佳方法是什么?

1 个答案:

答案 0 :(得分:3)

为您的任务命名包括key.id()将阻止其发送两次:

task_name = ''.join(['myemail-', str(mykey)])
try:
    taskqueue.Task(
        url="/someURL/send-single-email",
        name=task_name,
        method="POST",
        params={
           "subject": subject,  
           "body": body,
           "to": to,
           "from": from }
    ).add(queue_name="mail-queue")
except:
    pass #throws TombstonedTaskError(InvalidTaskError) if tombstoned name used.

有时您可能希望为具有相同密钥的邮件发送后续电子邮件。因此,我建议在任务名称中添加datedatetime戳记。这将允许您稍后发送相同密钥的其他消息:

 task_name = ''.join(['myemail-', str(mykey), str(datetime.utcnow()-timedelta(hours=8))]).translate(string.maketrans('.:_ ', '----'))