如何避免使用mailgun,taskqueue和ndb发送重复的电子邮件?

时间:2019-04-25 18:21:41

标签: app-engine-ndb mailgun task-queue

我正在使用taskqueue API通过mailgun来发送多封电子邮件。我的代码大致如下所示:

class CpMsg(ndb.Model):
    group = ndb.KeyProperty()
    sent = ndb.BooleanProperty()
    #Other properties


def send_mail(messages):
    """Sends a request to mailgun's API"""
    # Some code
    pass


class MailTask(TaskHandler):
    def post(self):
        p_key = utils.key_from_string(self.request.get('p'))
        msgs = CpMsg.query(
            CpMsg.group==p_key,
            CpMsg.sent==False).fetch(BATCH_SIZE)

        if msgs:
            send_mail(msgs)

            for msg in msgs:
                msg.sent = True

            ndb.put_multi(msgs)

            #Call the task again in COOLDOWN seconds

上面的代码运行良好,但是根据文档,taskqueue API保证至少要一次 交付任务,因此任务应该是幂等的。现在,在大多数情况下,上面的代码就是这种情况,因为它只获取“已发送”属性等于False的消息。问题在于非祖先的ndb查询最终只会保持一致,这意味着如果快速连续执行两次任务,查询可能会返回陈旧的结果并包含刚刚发送的消息。

我曾考虑为消息添加一个祖先,但是由于发送的电子邮件将成千上万,因此我担心这可能意味着拥有大型实体组,而这些实体组的写吞吐量有限。

我应该使用祖先进行查询吗?也许有一种方法可以配置mailgun以避免两次发送相同的电子邮件?我应该接受这样的风险吗?在极少数情况下,可能会多次发送几封电子邮件?

1 个答案:

答案 0 :(得分:1)

避免最终一致性障碍的一种可能方法是使查询成为keys_only,然后遍历消息键以通过键查找(强一致性)获取实际消息,检查{{1} }为True,在这种情况下跳过发送这些消息。遵循以下原则:

msg.sent

您还必须使 msg_keys = CpMsg.query( CpMsg.group==p_key, CpMsg.sent==False).fetch(BATCH_SIZE, keys_only=True) if not msg_keys: return msgs = ndb.get_multi(msg_keys) msgs_to_send = [] for msg in msgs: if not msg.sent: msgs_to_send.append(msg) if msgs_to_send: send_mail(msgs_to_send) for msg in msgs_to_send: msg.sent = True ndb.put_multi(msgs_to_send) 呼叫具有事务性(使用post装饰器)。

这应该解决由查询最终一致性导致的重复项。但是,由于数据存储区争用(或任何其他原因),由于事务重试而导致的重复项仍有空间-因为@ndb.transactional()调用不是幂等的。一次发送一条消息(也许使用任务队列)可以减少发生这种情况的机会。另请参见GAE/P: Transaction safety with API calls