可重入锁

加锁原理

  1. 根据key计算slot选择要将数据存到哪台redis实例上
  2. 执行加锁逻辑的lua脚本,使用redis中的hash结构(key : connId + “:” + threadId : num)。key如果不存在就设置为1,如果持有锁的是当前线程,就对值+1。否则返回key的剩余存活时间。默认key的过期时间为30s,由lockWatchdogTimeout参数控制

    1. if (redis.call('exists', KEYS[1]) == 0) then
    2. redis.call('hset', KEYS[1], ARGV[2], 1);
    3. redis.call('pexpire', KEYS[1], ARGV[1]);
    4. return nil;
    5. end;
    6. if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then r
    7. edis.call('hincrby', KEYS[1], ARGV[2], 1);
    8. redis.call('pexpire', KEYS[1], ARGV[1]);
    9. return nil;
    10. end;
    11. return redis.call('pttl', KEYS[1]);
  3. 如果加锁成功就注册watchdog,由watch dog定时为该key进行续期。续期的间隔时间为key的过期时间 / 3

  4. 如果加锁失败就进入忙循环进行重试

Redis-可重入锁.png

解锁原理

核心逻辑是需要对key进行-1操作,如果减到了0,则释放锁,否则不释放锁

if (redis.call('exists', KEYS[1]) == 0) then 
  redis.call('publish', KEYS[2], ARGV[1]); 
  return 1; 
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then 
  return nil;
end; 
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) then r
  edis.call('pexpire', KEYS[1], ARGV[2]); 
  return 0; 
else redis.call('del', KEYS[1]); 
  redis.call('publish', KEYS[2], ARGV[1]); 
  return 1; 
end; 
return nil;

公平锁

基本数据结构

一个基于redis的公平锁需要3个数据结构hash、list、zset
hash:主要由key:{连接id + “:” + 线程id : count},count值用于实现可重入,当同一线程第二次加锁,就会对count+1
list:用于存储加锁失败的线程,当持有锁的线程释放了锁之后只有队首的元素可以加锁成功
zset:每个尝试加锁失败的线程加入zset,并且score设置为currentTime + threadWaitTime(5s)

Redis-公平锁.png

加锁原理

  1. 尝试进行加锁时,需将过期的队首元素出队,直到队首元素未过期(被出队元素会通过忙循环再次进入)
  2. 如果当前没有锁,则判断队列是否为空(或队首元素为当前线程),如果是则成功抢到锁
  3. 如果当前锁占有且队首元素不是当前线程,需判断持有锁的是否是当前线程,如果是,则进行重入操作
  4. 如果持有锁的不是当前线程,则计算剩余时间,并将当前线程入队。如果当前线程为队首元素,则ttl时间为当前时间+锁剩余时间+threadWaitTime 。如果当前线程不是队首元素则为当前时间+队首元素ttl+threadWaitTime

Redis-公平锁加锁逻辑.png

  • ARGV[1] = 30s 线程持有锁的时间,如果没有设置则为30s并且由后台的watch dog进行续期
  • ARGV[2] = connId : threadId
  • ARGV[3] = currentTime + threadWaitTime(5s) (时间戳)
  • ARGV[4] = currentTime(时间戳)
  • KEYS[1] = lockName 锁的名称
  • KEYS[2] = redisson_lock_queue:{lockName}
  • KEYS[3] = redisson_lock_timeout:{lockName}
    while true do 
    local firstThreadId2 = redis.call('lindex', KEYS[2], 0);
    if firstThreadId2 == false 
      then break;
    end; 
    local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));
    if timeout <= tonumber(ARGV[4]) 
      then 
      redis.call('zrem', KEYS[3], firstThreadId2); 
      redis.call('lpop', KEYS[2]);         
    else 
      break;
    end; 
    end;
    if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) 
    then 
    redis.call('lpop', KEYS[2]); 
    redis.call('zrem', KEYS[3], ARGV[2]); 
    redis.call('hset', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
    end; 
    if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) 
    then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
    end; 
    local firstThreadId = redis.call('lindex', KEYS[2], 0); 
    local ttl; 
    if firstThreadId ~= false and firstThreadId ~= ARGV[2] 
    then 
    ttl = tonumber(redis.call('zscore', KEYS[3], firstThreadId)) - tonumber(ARGV[4]);
    else 
    ttl = redis.call('pttl', KEYS[1]);
    end; 
    local timeout = ttl + tonumber(ARGV[3]);
    if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 
    then 
    redis.call('rpush', KEYS[2], ARGV[2]);
    end; 
    return ttl;
    

    解锁原理

    核心逻辑是需要对key进行-1操作,如果减到了0,则释放锁,否则不释放锁
    if (redis.call('exists', KEYS[1]) == 0) then 
    redis.call('publish', KEYS[2], ARGV[1]); 
    return 1; 
    end;
    if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then 
    return nil;
    end; 
    local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
    if (counter > 0) then r
    edis.call('pexpire', KEYS[1], ARGV[2]); 
    return 0; 
    else redis.call('del', KEYS[1]); 
    redis.call('publish', KEYS[2], ARGV[1]); 
    return 1; 
    end; 
    return nil;
    

    MultiLock

    加锁原理

    MultiLock是通过对每一把锁进行循环上锁实现的上锁逻辑,总超时时间不能超过 锁数量 * 1.5s

    解锁原理

    通过循环所有锁释放