什么是分布式锁?
对于单机多线程,我们使用 ReentrantLock 这类本地锁来控制多个线程对本地共享资源的访问;而对于分布式系统,我们使用 分布式锁 来控制多个服务对共享资源的访问。
分布式锁基本要求:
1、互斥
2、高可用:锁服务时高可用的,即使获取锁的客户端出错,锁也一定会被释放,不影响其他线程对资源的访问。
一般选择基于 Redis 或者 ZooKeeper 实现分布式锁,Redis 更多,这里介绍以 Redis 为例的分布式锁实现。
基于 Redis 实现分布式锁
如何基于 Redis 是先用一个最简单的分布式锁?
Redis 中,SETNX 指令可以为我们实现最简单的分布式锁。
SETNX 即 set if not exist,如果 key 不存在,设置 key 的值;如果 key 存在,什么都不做。
> SETNX lockKey uniqueValue
(integer) 1
> SETNX lockKey uniqueValue
(integer) 0
释放锁,直接通过 DEL 命令删除对应 key 即可。
> DEL lockKey
(integer) 1
为了防止误删其他的锁,我们可以通过 Lua 脚本进行判断;Redis 在执行 Lua 脚本的时候,可以以原子性的方式执行,从而保证锁释放的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
为什么要给锁设置一个过期时间?
避免锁无法被释放,产生死锁。
127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX
OK
EX 3:过期时间为 3s(对 EX 对应的是 PX,单位为毫秒)
NX:key 值不存在才能成功
一定要保证设置指定 key 的值和过期时间是一个原子操作!!!不然可能出现锁无法被释放的问题。
我们可以使用 Redisson,它有一个看门狗 watch dog 的机制,可以自动给锁续期。
以 Redisson 的分布式可重入锁 RLock 为例来说明如何使用 Redisson 实现分布式锁:
// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁,具有 Watch Dog ⾃动续期机制
lock.lock();
// 3.执⾏业务
...
// 4.释放锁
lock.unlock();
非常简洁!
Redis 如何解决集群情况下分布式锁的可靠性?
Redis 通常是集群部署的。
解决方案:Redlock
让客户端向 Redis 集群中的多个独立 Redis 实例请求申请加锁,如果能和半数以上的实例完成加锁,那么我们认为客户端加锁成功,否则失败。
即使部分 Redis 节点不可用,只要保证半数以上可用,分布式锁的服务就是正常的。
Redlock 实现复杂,性能差,存在安全性隐患,实际项目中不建议使用 Redlock 来保证 Redis 集群下的分布式锁可靠性。
如果必须要实现可靠的分布式锁的话,可以基于 ZooKeeper 来做,只是性能会比 Redis 单点要差一些。