GAE/P:API 调用的交易安全

GAE/P: Transaction safety with API calls

假设您使用交易来处理 Stripe 付款并更新用户实体:

@ndb.transactional
def process_payment(user_key, amount):
    user = user_key.get()
    user.stripe_payment(amount) # API call to Stripe
    user.balance += amount
    user.put()

有可能 Stripe API 调用成功但 put 由于争用而失败。然后将向用户收费,但他的帐户不会反映付款。

您可以将 Stripe API 调用从交易中拉出,然后再进行交易,但您似乎仍然遇到同样的问题。扣款成功但交易失败,用户账户未入账。

这似乎是一个非常常见的场景。如何正确处理这一问题?

为了正确操作,事务函数需要是幂等的。所以你不能在这样的函数内部进行条带调用,因为它会使它成为非幂等的。

我会将条带调用分开,在 API 成功时,我会调用事务函数来更新用户的帐户余额(在发生争用时可以安全地重试)。

甚至可以创建一个单独的独立实体来反映条带 API 调用结果?这样的实体 应该 没有争用的余地,因为它只被写入一次 - 当条带事务发生时。这将使您能够:

  • 保留帐户交易历史记录 - 指向这些实体
  • 进行一些健全性检查以寻找孤立的条带交易(如果出于某种原因即使重试后帐户交易调用仍失败)并采取措施。

@thebjorn 的评论很好:多步骤方法可以使过程非常可靠:

  • 一个交易功能更新帐户以执行条带交易,这也 transactionally enqueues a push task 执行条带 API 调用。任务只有在事务成功时才会入队
  • 推送队列任务调用条带 API(最终创建条带交易实体),成功后,调用交易功能更新账户余额