《Redis深度历险》二(分布式锁)

分布式锁

redis命令积累

  • expire key 600; 设置超时时间
  • ttl key 查看超时时间

本质上就是在redis里面占一个坑,别的进程要进来时,发现已经有人在了,只好放弃或稍后再试。

  1. setnx

    setnx lock true
    do sth
    del lock
    

    问题:如果中间逻辑出现异常,del将会无法执行,会陷入死锁

  2. setnx lock true
    expire 5
    do sth
    del lock
    

    问题:如果setnx和expire之间断掉,没有执行expre,陷入死锁

  3. 解决: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")  # 完全释放锁
posted @ 2020-11-15 11:22  Jimmyhe  阅读(147)  评论(0编辑  收藏  举报