【问题标题】:In GAE, how to make money transfer transaction idempotent?在GAE中,如何使转账交易具有幂等性?
【发布时间】:2018-03-05 00:48:40
【问题描述】:

GAE 文档警告:

尽可能使您的数据存储事务具有幂等性,这样如果您重复事务,最终结果将是相同的。

假设我想在两个人之间转移一笔钱:

class User(ndb.Model):
    balance = ndb.IntegerProperty(default=0)

@ndb.transactional(xg=True)
def transfer(from_key, to_key, amount)
    from = from_key.get()
    to = to_key.get()
    from.balance -= amount
    to.balance += amount
    ndb.put_multi([from, to])

由于这不是幂等的,它可能会发生不止一次并导致问题。我想重构它以确保事务是幂等的。

answer 提出了一个解决方案:

要解决此问题,您可以通过创建“交易密钥”并将该密钥作为交易的一部分记录在新实体中来使交易具有幂等性。第二个事务可以检查该事务密钥,如果找到,则什么也不做。一旦您对交易完成感到满意,或者您放弃重试,就可以删除交易密钥。

但我不明白如何实现它。

有人能解释一下如何使这个事务具有幂等性吗?

【问题讨论】:

标签: google-app-engine transactions google-cloud-datastore


【解决方案1】:

您可以根据交易详情创建密钥,例如:

import datetime
import hashlib

>>> txn = {
    'from_account': '100123',
    'to_account': '200456',
    'amount': 123456,
    'timestamp': datetime.datetime(2017, 9, 23, 10, 11, 12, 123456)
}

# Combine the values into a string
>>> raw_key = u''.join([unicode(v) for k, v in sorted(txn.items())])

>>> print raw_key
1234561001232017-09-23 10:11:12.123456200456

# hash the key so exposing it in logs etc. doesn't expose transaction data
>>> key = hashlib.sha256().hexdigest()
>>> print key
261c516faa580d6604850967c5804f3fce5f323aae90e36debdb84aa0b950dcb

您可以将散列键存储在数据存储中,或者将其作为事务模型的计算属性(如果有),并在尝试创建新事务之前对其进行查询。

class TransactionKeys(ndb.model):
        pass


class TransactionHandler(webapp2.RequestHandler):

   def post(self):
       txn = {
            'from_account': self.request.POST['from'],
            'to_account': self.request.POST['to'],
            'value': self.request.POST['value']
            'timestamp': datetime.datetime.now()
       }
       raw_key = u''.join([unicode(v) for k, v in sorted(txn.items())])
       txn_key = hashlib.sha256().hexdigest()
       ...
       transfer(from_key, to_key, amount, txn_key)


@ndb.transactional(xg=True)
def transfer(from_key, to_key, amount, txn_key)
    already_exists = TransactionKeys.get_by_id(txn_key)
    if already_exists:
        raise DuplicateTransactionError('Duplicate transaction!')
    else:
        transaction_key = TransactionKey(id=txn_key)
    from = from_key.get()
    to = to_key.get()
    from.balance -= amount
    to.balance += amount
    ndb.put_multi([from, to, txn_key])

这种方法并不完美——例如,如果两个相同的事务在同一微秒内到达,它将失败。您可以添加其他数据以使密钥更加独特,例如 App Engine 实例 ID 或请求 ID。

最后,强制性免责声明:我不是安全专业人士,如果你用真钱做这件事,你应该进行适当程度的尽职调查并考虑专业赔偿/公共责任保险。

【讨论】:

  • 您能否详细说明您将如何使用密钥? (我的情况实际上并没有什么风险,但汇款是一个简单的例子。)
  • 由于您从不使用密钥中的信息,您能否只为 txn 密钥使用随机 UUID?此外,您似乎可以删除 post 末尾的 TransactionKeys 实体以进行清理。
  • 思路是key是从交易明细生成的,所以如果以后重放同一个交易,key会匹配;如果您可以使用 uuid 可靠地标记事务,则可以使用 uuid(尚不清楚重放是来自系统内部还是外部)。您可以在交易结束时删除密钥,但如果再次重播传输怎么办?
猜你喜欢
  • 2013-07-17
  • 2011-02-05
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-08
  • 2022-07-08
相关资源
最近更新 更多