在我的上一篇博客 stringRedisTemplate实现分布式锁中,由于没有考虑到原子性的问题,所以这篇博客将简单版的分布式锁实现出来.
还是老样子,学习一个技术分三步 是什么? 怎么用? 为什么?.
1 什么是分布式锁?
在单机应用下的并发编程中,我们通过锁(synchronized 、Lock
),来避免由于竞争而造成的数据不一致问题。而分布式锁,就是为了解决分布式应用中的数据不一致问题.通常使用redis分布式锁.
分布式锁的可靠性
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
-
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
2 redis分布式锁怎么用?
加锁
加锁方法需要传递四个参数(redis对象 ,key值 ,请求者id ,过期时间)
public static boolean addLock(Jedis jedis, String key, String requestId, int expireTime ){
//判断值是否为null 或者空
if("".equals(key)||"".equals(requestId)||null==requestId||null==key){
return false;
}
//判断过期时间是否合法
if(0>expireTime){
return false;
}
//加锁
String result=jedis.set(key,requestId,"NX","PX",expireTime);
if("ok".equals(result)){
return true;
}
return false;
}
参数解释:
1:redis就不用说了,处理锁用的
2:key值,用来保证唯一性
3:请求者id: 这个requestId 是用来保证 解铃还须系铃人这个特性的.加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
4:第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
5:第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
6: 过期时间
解锁
解锁方法有三个参数(redis对象,key值,请求者id)
//常量用来判断是否解锁成功
private static final Long RELEASE_SUCCESS = 1L;
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//Lua脚本代码
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//解锁
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
解释:
参数和加锁的一样,就不说了.
这里需要说的是lua脚本
Lua脚本代码,我们写了一个简单的Lua脚本代码, 首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的。
eval代码,我们将Lua代码传到jedis.eval()
方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。那么为什么执行eval()方法可以确保原子性? 简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
到这里redis分布式锁就完成了. 感谢老哥的指正.
参考网址:https://blog.csdn.net/yb223731/article/details/90349502