Redis分布式锁
redis分布式锁原理
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
使用setnx、getset、expire、del这4个redis命令实现
- setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。 命令格式:SETNX key value;使用:只在键 key 不存在的情况下,将键 key 的值设置为 value 。若键 key 已经存在, 则 SETNX 命令不做任何动作。返回值:命令在设置成功时返回 1 ,设置失败时返回 0 。
- etset 命令格式:GETSET key value,将键 key 的值设为 value ,并返回键 key 在被设置之前的旧的value。返回值:如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil 。当键 key 存在但不是字符串类型时,命令返回一个错误。
- expire 命令格式:EXPIRE key seconds,使用:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。返回值:设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。
- del 命令格式:DEL key [key …],使用:删除给定的一个或多个 key ,不存在的 key 会被忽略。返回值:被删除 key 的数量。
原理图如下所示:
Redis分布式锁要注意的地方
- 互斥性:在任意时间都只能有一个客户端可以获取锁
- 防死锁:加入一个客户端在持有锁的时候崩溃了,没有释放锁,那么别的客户端无法获得锁,就会造成死锁,所以一定要保证客户端一定要释放锁。
- 持锁人解锁;加锁和解锁必须是同一个客户端,客户端不能解开别的客户端的锁
- 可重入:当一个客户端获取锁对象之后,这个客户端可以再次获取这个对象上的锁。
加锁和解锁
加锁:
- 1)setnx命令加锁
set if not exists 我们会用到Redis的命令setnx,setnx的含义就是只有锁不存在的 情况下才会设置成功。 - 2)设置锁的有效时间,防止死锁 expire
不过为了保证原子性,这里使用
set(key, id, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)
END_TIME代表获取锁的时间,若是时间到了就放弃获取锁。
SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
SET_WITH_EXPIRE_TIME,给这个key加一个过期的设置,具体时间由第五个参数决定
LOCK_SUCCESS和RELESE_SUCCESS分别和jedis.set的返回值和jedis.eval的返回值进行比较
解锁:
1)检查是否自己持有锁(判断唯一标识);
2)删除锁。
解锁也是两步,同样也要保证解锁的原子性,把两步合为一步。
这就无法借助于Redis了,只能依靠Lua脚本来实现。
if Redis.call("get",key==argv[1])then
return Redis.call("del",key)
else return 0 end
这就是一段判断是否自己持有锁并释放锁的Lua脚本
为什么Lua脚本是原子性呢?因为Lua脚本是jedis用eval()函数执行的,如果执行则会全部执行完成。
代码实现:
其中RedisPool是自己封装的连接池,具体实现在上一篇博客
RedisPool连接池实现
/**
* redis分布式锁
* @author by zzj
* @date 2019/11/28.
*/
public class JedisDistributeLock {
private static final int END_TIME = 1000;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";