可重入锁
加锁原理
- 根据key计算slot选择要将数据存到哪台redis实例上
执行加锁逻辑的lua脚本,使用redis中的hash结构(key : connId + “:” + threadId : num)。key如果不存在就设置为1,如果持有锁的是当前线程,就对值+1。否则返回key的剩余存活时间。默认key的过期时间为30s,由lockWatchdogTimeout参数控制
if (redis.call('exists', KEYS[1]) == 0) thenredis.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;return redis.call('pttl', KEYS[1]);
如果加锁成功就注册watchdog,由watch dog定时为该key进行续期。续期的间隔时间为key的过期时间 / 3
- 如果加锁失败就进入忙循环进行重试
解锁原理
核心逻辑是需要对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)
加锁原理
- 尝试进行加锁时,需将过期的队首元素出队,直到队首元素未过期(被出队元素会通过忙循环再次进入)
- 如果当前没有锁,则判断队列是否为空(或队首元素为当前线程),如果是则成功抢到锁
- 如果当前锁占有且队首元素不是当前线程,需判断持有锁的是否是当前线程,如果是,则进行重入操作
- 如果持有锁的不是当前线程,则计算剩余时间,并将当前线程入队。如果当前线程为队首元素,则ttl时间为当前时间+锁剩余时间+threadWaitTime 。如果当前线程不是队首元素则为当前时间+队首元素ttl+threadWaitTime

- 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解锁原理
通过循环所有锁释放
