分布式锁的实现方式
分布式锁的实现方式一般分为3种:数据库乐观锁、基于redis的分布式锁、基于zookeeper的分布式锁
为了确保分布式锁可用,至少要保证所得实现满足4种条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁期间挂掉没有主动释放锁,也要保证后续其他客户端可以加锁。
3.具有容错性。只要大部分的redis节点正常运行,客户端就可以加锁和解锁。
4.加锁和解锁必须保证是同一个客户端,不能把别人的锁解除了
基于redis的分布式锁
加锁
参考网上例子,写出以下代码
redisService.set(key, value, “nx”, “ex”, time) 这个是加锁的代码
key 为锁的唯一标识。
value 为验证客户端唯一性的参数,可以使用 UUID.randomUUID().toString() 获取。
nx 为SET IF NOT EXIST,key 存在时不做任何操作。
ex 为设置过期时间,具体时间由第五个参数决定。
time 为过期时间。
解锁
用上面生成的 value 和redis里面获取的 value 进行比较,相同则表示是相同的客户端,之后释放锁即可。
这里的问题是分为两步操作,不是原子性的,当第一步执行完成后锁可能因为过期销毁,就可能会发生另一个客户端刚拿到锁就被释放的问题。
最好的解决方式是通过 redis 的 eval() 方法,这个方法可以执行一段脚本,脚本运行是原子性的,在脚本运行期间没有客户端可以操作,下面为拷贝过来的代码
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
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;
}
}
关于 eval() 方法的参数,大致有四个参数
1.script :一段需要执行的脚本
2.numkeys: 用于指定键名参数的个数
3.key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
4.arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。