《Redis深度历险》二(分布式锁)
分布式锁
redis命令积累
- expire key 600; 设置超时时间
- ttl key 查看超时时间
本质上就是在redis里面占一个坑,别的进程要进来时,发现已经有人在了,只好放弃或稍后再试。
-
setnx
setnx lock true do sth del lock
问题:如果中间逻辑出现异常,del将会无法执行,会陷入死锁
-
setnx lock true expire 5 do sth del lock
问题:如果setnx和expire之间断掉,没有执行expre,陷入死锁
-
解决:setnx和expire组合的原子指令,也是分布式锁的奥义
set lock true ex 5 nx do sth del lock
超时问题
redis分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑执行的太长,以至于超出了锁的超时限制,就会有问题。
解决:使用delifequals释放锁,而不是expire,可以传入key和一个随机数来保证释放锁的正确性。
tag = random.nextint() // random tag
if redis.set(key,tag,nx=True,ex=5):
do_sth
redis.delifequals(key,tag)
可重入性
可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加 锁,那么这个锁就是可重入的。Redis 分 布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用线程的 Threadlocal 变量 存储当前持有锁的计数。
import redis import threading
locks = threading.local()
locks.redis = {}
def key_for(user_id):
return "account_{}".format(user_id)
def _lock(client,key):
return bool(client.set(key,True,nx=True,ex=5))
def _unlock(client,key):
client.delete(key)
def lock(client,user_id):
key = key_for(user_id)
if key in locks.redis: # 若存在则锁的数量+1
locks.redis[key] += 1
return True
ok = _lock(client,key) # 若不存新增锁,数量设为1
if not ok:
retrun False
locks.redis[key] = 1
return True
def unlock(client,user_id):
key = key_for(user_id)
if key in locks.redis:
locks.redis[key] -= 1 # 释放锁,数量-1
if locks.redis[key] <= 0: # 判断若小于0,则完全释放锁
del locks.redis[key]
return True
return False
client = redis.StrictRedis()
lock(client,"codehole") # 第一次加锁,返回True
lock(client,"codehole") # 第二次,锁的数量+1,返回True
unlock(client,"codehole") # 释放一把锁
unlock(client,"codehole") # 完全释放锁