1

基本上,我有以下问题。有一个处理付款的订单处理系统。在极少数情况下,我们最终会收到双重订单,因为当我们通过 API 查询信用卡处理程序时,如果用户刷新页面非常快,信用卡处理程序有时会将这两个请求都响应为“成功”,我们得到两个成功系统中的“付费”事件。

所以我的想法是对支付的东西(每个订单)实施锁定,如果锁定被锁定,告诉客户关闭(如果客户很快刷新页面就会发生这种情况 - 我认为在我们的例子中它实际上是故意的) .

所以我想用 Redis 来做这件事并想出了这个:

def _PaymentInterlock(object):
    def __init__(self, pp):
        self.key = GlobalKey('pay_ilk_%s'%pp._ident)

    def lock(self):
        self.key(1)

    def unlock(self):
        self.key.delete()

    def try_lock(self):
        result = self.key()
        if result == 1:
            return False
        self.lock()
        return True

唯一的问题是try_lock操作不是原子的(与真正的比较和存储操作相反),所以从技术上讲,两个 WSGI 工作人员可能会丢失密钥,然后都锁定“锁”,从而导致相同类型的问题。

对我将如何解决此类问题有任何建议吗?

4

1 回答 1

1

由于 Redis 的单线程特性,锁定 Redis 实际上非常容易。只需使用SetNX。该链接页面上有更多信息,但基本思想是:

  1. Client1 请求锁定。如果它得到它,它会将锁名称设置为某个唯一的 transaction_id。该值应该是当前时间。
  2. Client2(可能是用户刷新)请求锁定同名。它不接收它。
  3. 作为一种退避措施,client2 检查锁上的时间戳。如果它超过了某个 max_time,这意味着由于某种原因 client1 没有正确释放它的锁,所以我们无论如何都会用新的时间戳将锁给 client2。否则,client2 不走运,不会继续。
  4. Client1 完成它正在做的任何事情(在你的情况下是事务)并释​​放锁,所以现在它可以再次访问,以防用户想要对事务执行其他操作。

这是此设计模式的简化/修改版本。我在生产中使用了非常相似的东西:

def getRedisLock(name, max_time, r):
    lock = r.setnx(name, int(time.mktime(time.gmtime())))
    if not lock:
        lock_time = int(r.get(name))
        #if the lock expired (assume some client failed)
        if lock_time + max_time < int(time.mktime(time.gmtime())):
            old_time = int(r.getset(name, int(time.mktime(time.gmtime()))))
            if old_time == lock_time:
                lock = True
    #If you still have no lock, do something special here, if you want
    if not lock:
        pass
    return lock

def releaseRedisLock(self, name):
    r = self.r
    return r.delete(name)

只需在每个事务之前使用一些唯一的 transaction_id 调用 getRedisLock() 即可。仅在您获得锁时才处理信用卡,否则告诉用户不要再做混蛋了:)

于 2013-08-20T23:18:05.747 回答