基于redis setnx的简易分布式锁 修正版

               

前面写过一篇  错误示例 , 当时写完测试方法不对,就直接放上来了.

后面实际使用过程中发现不行尴尬, 这次将改正后的记录下来.


前一篇写了一些锁的概念和为了防止死锁而使用redis对key的有效期来控制超时释放.
这些都是没有问题的.

而上一篇出问题的地方,基本上也是现在网上很多帖子的问题所在.

问题1: 获取锁



原来的获取锁, 是使用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;   }


问题2: 释放锁


我们原来是查询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

要多看官方文档!!!



           

猜你喜欢

转载自blog.csdn.net/qq_44952610/article/details/89470425