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

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

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


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

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

问题1: 获取锁

基于redis setnx的简易分布式锁(修正版)_第1张图片


原来的获取锁, 是使用setnx 和 expire 两条命令来实现的, 这不是个原子操作. 所以就会导致这过程中出很多问题.

虽然我们上一版对这个做了一些判断, 但是都是无用功, 因为那些判断都不能保证这两条命令的原子性.

但是我们看了redis的api,setnx 命令又没有 expire参数, 怎么办呢?

我们在redis官网看下 setnx命令的说明, 里面有一段描述

基于redis setnx的简易分布式锁(修正版)_第2张图片

这里说明从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: 释放锁

基于redis setnx的简易分布式锁(修正版)_第3张图片

我们原来是查询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 setnx的简易分布式锁(修正版)_第4张图片

代码里有多线程测试的用例,可以看看.

基于redis setnx的简易分布式锁(修正版)_第5张图片


上面这些都是基于单机redis的,更详细的或者多台redis的,可以看官网的文档

https://redis.io/topics/distlock

要多看官方文档!!!



你可能感兴趣的:(锁)