说说 Redisson 分布式锁的原理?
说说 Redisson 分布式锁的原理?
回答重点
Redisson 是基于 Redis 实现的分布式锁,实际上是使用 Redis 的原子操作来确保多线程、多进程或多节点系统中,只有一个线程能获得锁,避免并发操作导致的数据不一致问题。
1)锁的获取:
- Redisson 使用 Lua 脚本,利用
exists+hexists+hincrby命令来保证只有一个线程能成功设置键(表示获得锁)。 - 同时,Redisson 会通过
pexpire命令为锁设置过期时间,防止因宕机等原因导致锁无法释放(即死锁问题)。
2)锁的续期:
- 为了防止锁在持有过程中过期导致其他线程抢占锁,Redisson 实现了锁自动续期的功能。持有锁的线程会定期续期,即更新锁的过期时间,确保任务没有完成时锁不会失效。
3)锁的释放:
- 锁释放时,Redisson 也是通过 Lua 脚本保证释放操作的原子性。利用
hexists+del确保只有持有锁的线程才能释放锁,防止误释放锁的情况。 - Lua 脚本同时利用
publish命令,广播唤醒其它等待的线程。
4)可重入锁:
- Redisson 支持可重入锁,持有锁的线程可以多次获取同一把锁而不会被阻塞。具体是利用 Redis 中的哈希结构,哈希中的 key 为线程 ID,如果重入则
value +1,如果释放则value -1,减到 0 说明锁被释放了,则del锁。
扩展知识
Redisson 分布式锁 lua 相关源码解析
加锁的代码:
1 | <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { |
hincrby: 如果哈希表的 key 不存在,会创建新的哈希表并执行 hincrby 命令
if + or 的逻辑可能不好理解,我把上述的 lua 脚本拆开来看(以下脚本的效果同上):
1 | if (redis.call('exists', KEYS[1]) == 0) then |
上述的 lua 脚本含义如下:
1)若锁不存在,则新增锁,并设置锁重入计数为 1 且设置锁过期时间
1 | if (redis.call('exists', KEYS[1]) == 0) then |
2)若锁存在,且唯一标识(线程id相关)也匹配,则当前加锁请求为锁的重入请求,哈希的重入计数+1,并再次设置锁过期时间
1 | if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then |
3)若锁存在,但唯一标识不匹配,则说明锁被其他线程占用,当前线程无法解锁,直接返回锁剩余过期时间(pttl)
1 | return redis.call('pttl', KEYS[1]); |
上述脚本中,几个参数的含义如下:
1 | KEYS[1] = Collections.singletonList(getRawName()) |
释放锁的代码:
1 | protected RFuture<Boolean> unlockInnerAsync(long threadId) { |
上述的 lua 脚本含义如下:
1)若锁不存在,直接返回不需要解锁
1 | if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then |
2)若锁存在,且唯一标识(线程id相关)也匹配,计数 - 1,如果此时计数还大于 0 ,再次设置锁过期时间
1 | local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); |
3)若计数小于等于 0 ,则删除 key ,且通过广播通知其它等待锁的线程,此时释放锁了
1 | else |
上述脚本中,几个参数的含义如下:
Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)
1 | KEYS[1] = getRawName() |
Redisson 的锁类型:
- 公平锁:与可重入锁类似,公平锁确保多个线程按请求锁的顺序获得锁。
- 读写锁:支持读写分离。多个线程可以同时获得读锁,而写锁是独占的。
- 信号量与可数锁:允许多个线程同时持有锁,适用于资源的限流和控制。
Redis分布式锁
关于上述的 SETNX、Lua 脚本释放锁等,看参考下面这个面试题
锁过期和网络分区补充
- 锁过期问题:在 Redis 中,通过
SETNX获取的锁通常带有过期时间。如果业务逻辑执行时间超过锁的过期时间,可能会出现锁被其他线程重新获取的问题。Redisson 通过锁的续期机制解决了这个问题。 - 网络分区问题:Redis 是基于主从复制的,在主从切换或发生网络分区时,锁可能出现不一致的情况(如主节点锁还存在,副节点却因为主从切换失去了锁)。为解决这一问题,业界提出了 Redlock 算法,Redisson 也可以使用此算法确保锁的高可用性。
Comments