在Python Web应用程序中执行全局锁定?

时间:2013-08-20 21:52:27

标签: python redis uwsgi

基本上,我有以下问题。有一个处理付款的订单处理系统。在非常罕见的情况下,我们最终得到双重订单,因为当我们通过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工作者可以得到一个密钥未命中,然后锁定“锁定”导致同样的问题。

关于如何解决这类问题的任何建议吗?

1 个答案:

答案 0 :(得分:1)

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

  1. Client1要求锁定。如果它得到它,它将锁名称设置为一些唯一的transaction_id。该值应为当前时间。
  2. Client2(也许是用户刷新)要求锁定同一个名称。它没有收到它。
  3. 作为退避措施,client2会检查锁定的时间戳。如果它超过某个max_time,这意味着由于某种原因,client1没有正确释放其锁定,所以我们无论如何都使用新的时间戳将锁定给了client2。否则,client2运气不好,不能继续。
  4. Client1完成它正在做的任何事情(在您的情况下是事务)并释​​放锁定,所以现在它可以再次访问,以防用户想要对事务执行其他操作。
  5. 这是此设计模式的简化/修改版本。我在制作中使用了非常相似的东西:

    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()。如果您获得锁定,则只处理信用卡,否则告诉用户不要成为混蛋:)