1. 程式人生 > >python redis分布式鎖改進

python redis分布式鎖改進

release 代碼 dynamic blog python www name 運行 float

0X01 python redis分布式鎖通用方法

REDIS分布式鎖實現的方式:SETNX + GETSET

使用Redis SETNX 命令實現分布式鎖

python 版本實現上述思路(案例1)

Redis分布式鎖的python實現

但是,流通的代碼 redis鎖中有BUG,有考慮不周的點。

0X02 時間戳變為str形式

time.time() > cls.rdcon.get(cls.lock_key)

寫法不正確。time.time()為浮點型,redis get取得的為字符串型。

python 中,字符串與浮點型對比,字符串大。

>>> a = "0.003"
>>> b = 1000000
>>> a >b
True
>>> a < b
False
>>>
於是
if cls._lock == 1 or (time.time() > cls.rdcon.get(cls.lock_key) and time.time() > cls.rdcon.getset(cls.lock_key, timestamp)):
變成
if cls._lock == 1 or (time.time() > float(cls.rdcon.get(cls.lock_key)) and time.time() > float(cls.rdcon.getset(cls.lock_key, timestamp))):

0X03 競爭條件下的float

在代碼運行到任何一處地方,鎖都可能釋放。

比如,剛開始拿不到鎖, cls._lock!=1,走向or。

這時鎖釋放了,redis中取出了None,float(None)報錯。

或者getset獲得了None(說明寫入成功),float(None)報錯。

float中可能是數值型,也可能是None型。

改進(同時取函數的第二個參數作為標識id)

#!/usr/bin/env python
# coding=utf-8

import time
import redis
from conf.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD


class RedisLock(object):
    def __init__(self):
        self.rdcon = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=1)
        self._lock = 0
        self.lock_key = ""

    @staticmethod
    def my_float(timestamp):
        """

        Args:
            timestamp:

        Returns:
            float或者0
            如果取出的是None,說明原本鎖並沒人用,getset已經寫入,返回0,可以繼續操作。
        """
        if timestamp:
            return float(timestamp)
        else:
            return 0

    @staticmethod
    def get_lock(cls, key, timeout=10):
        cls.lock_key = "%s_dynamic_lock" % key
        while cls._lock != 1:
            timestamp = time.time() + timeout + 1
            cls._lock = cls.rdcon.setnx(cls.lock_key, timestamp)
            # if 條件中,可能在運行到or之後被釋放,也可能在and之後被釋放
            # 將導致 get到一個None,float失敗。
            if cls._lock == 1 or (
                            time.time() > cls.my_float(cls.rdcon.get(cls.lock_key)) and
                            time.time() > cls.my_float(cls.rdcon.getset(cls.lock_key, timestamp))):
                break
            else:
                time.sleep(0.3)

    @staticmethod
    def release(cls):
        if cls.rdcon.get(cls.lock_key) and time.time() < cls.rdcon.get(cls.lock_key):
            cls.rdcon.delete(cls.lock_key)


def redis_lock_deco(cls):
    def _deco(func):
        def __deco(*args, **kwargs):
            cls.get_lock(cls, args[1])
            try:
                return func(*args, **kwargs)
            finally:
                cls.release(cls)
        return __deco
    return _deco


@redis_lock_deco(RedisLock())
def my_func():
    print "myfunc() called."
    time.sleep(20)

if __name__ == "__main__":
    my_func()

python redis分布式鎖改進