def _create_lock_object(self, key):
'''
Returns a lock object, split for testing
'''
return redis_lock.Lock(self.redis_conn, key,
expire=self.settings['REDIS_LOCK_EXPIRATION'],
auto_renewal=True)
这是调用redis_lock.Lock返回了一个lock的对象。
对象获取锁的逻辑代码大致如下
def acquire(self, blocking=True, timeout=None):
busy = True
blpop_timeout = timeout or self._expire or 0
timed_out = False
while busy:
busy = not self._client.set(self._name, self._id, nx=True, ex=self._expire)
if busy:
if timed_out:
return False
elif blocking:
timed_out = not self._client.blpop(self._signal, blpop_timeout) and timeout
else:
logger.debug("Failed to get %r.", self._name)
return False
logger.debug("Got lock for %r.", self._name)
if self._lock_renewal_interval is not None:
self._start_lock_renewer()
return True
可以看到 busy是用于循环的条件,busy是基于redis的setnx命令完成的分布式锁。
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若key已经存在,则不做任何操作
利用key的插入唯一性,即可以完成锁的设置,争夺锁即争夺key的插入,有且只有一个插入操作可以完成。
回到上面代码,当set不成功时(为争到锁时),查看是否允许阻塞,阻塞即等待信号一个超时时间,不阻塞则立即返回false。
如下为封装redis执行set name value nx的代码:
def set(self, name, value, ex=None, px=None, nx=False, xx=False):
"""
Set the value at key ``name`` to ``value``
``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
``nx`` if set to True, set the value at key ``name`` to ``value`` if it
does not already exist.
``xx`` if set to True, set the value at key ``name`` to ``value`` if it
already exists.
"""
pieces = [name, value]
if ex:
pieces.append('EX')
if isinstance(ex, datetime.timedelta):
ex = ex.seconds + ex.days * 24 * 3600
pieces.append(ex)
if px:
pieces.append('PX')
if isinstance(px, datetime.timedelta):
ms = int(px.microseconds / 1000)
px = (px.seconds + px.days * 24 * 3600) * 1000 + ms
pieces.append(px)
if nx:
pieces.append('NX')
if xx:
pieces.append('XX')
return self.execute_command('SET', *pieces)
释放锁
def release(self):
"""Releases the lock, that was acquired with the same object.
.. note::
If you want to release a lock that you acquired in a different place you have two choices:
* Use ``Lock("name", id=id_from_other_place).release()``
* Use ``Lock("name").reset()``
"""
if self._lock_renewal_thread is not None:
self._stop_lock_renewer()
logger.debug("Releasing %r.", self._name)
error = _eval_script(self._client, UNLOCK, self._name, self._signal, args=(self._id,))
if error == 1:
raise NotAcquired("Lock %s is not acquired or it already expired." % self._name)
elif error:
raise RuntimeError("Unsupported error code %s from EXTEND script." % error)
else:
self._delete_signal()
def _delete_signal(self):
self._client.delete(self._signal)
这里先判断了一下 renewal,为true则会在设置的时间之内会对expire time进行更新,默认设置为float(2/3*expire)。然后即对key进行UNLOCK操作。
看看 UNLOCK如何进行
# 传入的参数为 (self._client, UNLOCK, self._name, self._signal, args=(self._id,))
def _eval_script(redis, script_id, *keys, **kwargs):
"""Tries to call ``EVALSHA`` with the `hash` and then, if it fails, calls
regular ``EVAL`` with the `script`.
"""
args = kwargs.pop('args', ()) # (self._id,)
if kwargs:
raise TypeError("Unexpected keyword arguments %s" % kwargs.keys())
try:
return redis.evalsha(SCRIPTS[script_id], len(keys), *keys + args)
#笔者注释加: SCRIPTS[script_id]=UNLOCK,len(keys)=2,keys=self._name, self._signal+(self._id,)
except NoScriptError:
logger.warn("%s not cached.", SCRIPTS[script_id + 2])
return redis.eval(SCRIPTS[script_id + 1], len(keys), *keys + args)
使用redis 的evalsha命令。redis 从2.6.0开始可以通过其内置的lua解释器运行lua脚本。其命令即为EVAL。SCRIPTS[script_id] 其实是传入’UNLOCK’使其可以找到下面对应的UNLOCK的lua脚本。
EVAL script numkeys key [key …] arg [arg …]
script 为lua脚本代码
numkeys 为传入的键名参数的个数,即key的个数。
key 为redis存入的键(KEY)。在lua脚本中可以通过1 为基址的形式访问,如key[1]…(以1开始哟~)形式获得其值
arg为 不是键名的参数。在lua脚本中可以通过args[1]…获得其值
lua脚本如下:
UNLOCK_SCRIPT = b"""
if redis.call("get", KEYS[1]) ~= ARGV[1] then
return 1
else
redis.call("del", KEYS[2])
redis.call("lpush", KEYS[2], 1)
redis.call("del", KEYS[1])
return 0
end
"""
UNLOCK_SCRIPT_HASH = sha1(UNLOCK_SCRIPT).hexdigest()