前面写过一篇 错误示例 , 当时写完测试方法不对,就直接放上来了.
后面实际使用过程中发现不行, 这次将改正后的记录下来.
前一篇写了一些锁的概念和为了防止死锁而使用redis对key的有效期来控制超时释放.
这些都是没有问题的.
而上一篇出问题的地方,基本上也是现在网上很多帖子的问题所在.
原来的获取锁, 是使用setnx 和 expire 两条命令来实现的, 这不是个原子操作. 所以就会导致这过程中出很多问题.
虽然我们上一版对这个做了一些判断, 但是都是无用功, 因为那些判断都不能保证这两条命令的原子性.
但是我们看了redis的api,setnx 命令又没有 expire参数, 怎么办呢?
我们在redis官网看下 setnx命令的说明, 里面有一段描述
这里说明从2.6.12版本后, 就可以使用set来获取锁, Lua 脚本来释放锁
setnx是老黄历了.
然后我们看下 set命令的说明 , 发现这里面可以有nx,xx等参数, 来实现 setnx 的功能.
而且这里再次提到了 获取锁和释放锁.
所以要多看官方文档!!!
所以,按照官方文档,我们获取锁就可以改版成这样
String result = jedis.set(key, value, "NX", "PX", expireMillis);
if (result != null && result.equalsIgnoreCase("OK")) {
flag = true;
}
我们原来是查询key,然后比较value,然后直接del
同样这些操作无法保证原子性.
在上面我们已经知道, 官网推荐的是 用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(fullKey), Collections.singletonList(value));
if (Objects.equals(UNLOCK_SUCCESS, result)) {
flag = true;
}
这里还在网上看到另外一种替代实现方式:
jedis.watch(fullKey);
String existValue = jedis.get(fullKey);
if (Objects.equals(value, existValue)) {
jedis.del(fullKey);
flag = true;
}
使用watch命令来检测获取key后,key有没有变动. 不过还是建议使用官方推荐的.
https://github.com/qq315737546/redis-lock
代码的LockUtil类,包含了lock,tryLock等方法
代码里有多线程测试的用例,可以看看.
上面这些都是基于单机redis的,更详细的或者多台redis的,可以看官网的文档
https://redis.io/topics/distlock
要多看官方文档!!!