前前言
这篇文章是我发送给redis翻译者的,然后过了一周也没收到回复。所以贴在了这里,欢迎大家一起讨论。
前言
我在第三次研究《redis实战》第六章分布式锁的内容的时候,对获得超时属性锁的函数中的一个操作深感怀疑。在不断的验证的过程中,觉得这个操作是有问题的。所以特意发来邮件询问,不知是否是我没有深刻的理解到分布式锁的操作。
背景
关于ttl
通过官方文档,可以知道ttl一个元素的时候,返回值为以秒为单位,返回给定 key 的剩余生存时间。
分布式锁
原始代码中的函数acquire_lock_with_timeout代码如下
def acquire_lock_with_timeout(
conn, lockname, acquire_timeout=10, lock_timeout=10):
# 128位随机标识符。
identifier = str(uuid.uuid4())
lockname = 'lock:' + lockname
# 确保传给EXPIRE的都是整数。
lock_timeout = int(math.ceil(lock_timeout))
end = time.time() + acquire_timeout
while time.time() < end:
# 获取锁并设置过期时间。
if conn.setnx(lockname, identifier):
conn.expire(lockname, lock_timeout)
return identifier
# 检查过期时间,并在有需要时对其进行更新。
elif not conn.ttl(lockname):
conn.expire(lockname, lock_timeout)
time.sleep(.001)
return False
此段代码中有如下一句,我是比较有疑惑的
# 检查过期时间,并在有需要时对其进行更新。
elif not conn.ttl(lockname):
conn.expire(lockname, lock_timeout)
疑虑部分的验证
not ttl(lockname)成立的时机
关于not conn.ttl(lockname)成立的时机,我做了如下代码验证
redis = redis.Redis()
redis.set('lock:market', 10)
redis.expire('lock:market', 1)
end_time = time.time() + 1.5
while time.time() < end_time:
res = redis.ttl('lock:market')
print 'res is {}, not res is {}, key is {}'.format(res, not res, redis.get('lock:market'))
time.sleep(0.001)
运行的结果通过uniq对重复的内容进行了剔除,然后结果如下
(venv) ➜ standard git:(master) ✗ python redis_lock/lock.py | uniq -c
284 res is 1, not res is False, key is 10
240 res is 0, not res is True, key is 10
1 res is 0, not res is True, key is None
248 res is -2, not res is False, key is None
根据打印的结果可以知道,在过期时间为0的时候,键还是存在的。有一行的结果过期时间为0,但是值不存在了。我认为是在获取过期时间的时候,还是存在的,而在执行redis.get('lock:market')的时候就已经过期了。
也就是会在lockname过期时间为0的时候,not conn.ttl(lockname)会成立,此时键还是存在的。
如果分布式环境中,有一个客户端获得锁,但是运行时间快要超时了(此时还没有超时),此时如果有其他客户端通过acquire_lock_with_timeout来获得锁,则会把原来的锁的过期时间给续上。因为在conn.setnx(lockname, identifier)为False(键还没过期,键还存在),not conn.ttl(lockname)为True(此时过期时间为0)的时候,会通过conn.expire(lockname, lock_timeout)给锁的过期时间延长。
验证
验证代码如下,在下面代码结束的时候,lock:market的值还是10,也就是说在一个客户端运行超时的时候,锁没有释放,而是通过acquire_lock_with_timeout给延长了
redis = redis.Redis()
redis.set('lock:market', 10)
redis.expire('lock:market', 1)
acquire_lock_with_timeout(redis, 'market')
结论
我觉得下面的代码是需要删在函数acquire_lock_with_timeout中删除掉的,不知道我的考虑是否是正确的,所以特地来发送邮件来询问。
# 检查过期时间,并在有需要时对其进行更新。
elif not conn.ttl(lockname):
conn.expire(lockname, lock_timeout)
或者是我不大懂注释中的有需要时是什么时候