redis加锁的方式有哪些

redis常见的加锁命令分别是INCR、SETNX、SET

1、INCR

这种锁的加锁思路是:

key不存在,那么key的值会先被初始化为0,然后再执行INCR操作进行加一。

Redis分布式锁的五种实现方式

然后其它用户在执行INCR操作进行加一时,如果返回的数值大于1,说明这个key正在被锁定使用。

1、 客户端A请求服务器获取key的值为1表示获取了锁

2、 客户端B也去请求服务器获取key的值为2表示获取锁失败

3、 客户端A执行代码完成,删除锁

4、 客户端B在等待一段时间后在去请求的时候获取key的值为1表示获取锁成功

5、 客户端B执行代码完成,删除锁

$redis->
incr($key);

$redis->
expire($key, $ttl);
//设置生成时间为1秒

具体使用命令:

127.0.0.1:6379>
INCR keyName 2、SETNX

这种加锁的思路是,如果key不存在,将key设置为value,如果key存在,则SETNX不做任何动作。

SETNX是SET if Not eXists的缩写。

1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功

2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败

3、 客户端A执行代码完成,删除锁

4、 客户端B在等待一段时间后在去请求设置key的值,设置成功

5、 客户端B执行代码完成,删除锁

$redis->
setNX($key, $value);

$redis->
expire($key, $ttl);

具体使用命令:

redis>
SETNX keyName value
(integer) 1

设置成功,返回1;设置失败,返回0

3、SET

上面两种方法都有一个问题,会发现,都需要设置 key 过期。

那么为什么要设置key过期呢?

如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。

于是乎我们需要给锁加一个过期时间以防不测。

但是借助 Expire 来设置就不是原子性操作了。

所以还可以通过事务来确保原子性,但是还是有些问题,所以官方就引用了另外一个,使用 SET 命令本身已经从版本 2.6.12 开始包含了设置过期时间的功能。

1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功

2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败

3、 客户端A执行代码完成,删除锁

4、 客户端B在等待一段时间后在去请求设置key的值,设置成功

5、 客户端B执行代码完成,删除锁

$redis->
set($key, $value, array('
nx'
, '
ex'
=>
$ttl));
//ex表示秒

具体使用:

redis>
set key value NX EX max-lock-time 实现加锁

命令解释:

  • key:key就是redis的key值作为锁的标识,value在这里作为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】

  • max-lock-time:通过max-lock-time设置过期时间保证不会出现死锁【避免死锁】

  • NX:只有这个key不存才的时候才会进行操作,if not exists;

  • EX:设置key的过期时间为秒,具体时间由第5个参数决定

加锁代码:

Jedis jedis = new Jedis("
127.0.0.1"
, 6379);

private static final String SUCCESS = "
OK"
;

/**
* 加锁操作
* @param key 锁标识
* @param value 客户端标识
* @param timeOut 过期时间
*/

public Boolean lock(String key,String value,Long timeOut){
String var1 = jedis.set(key,value,"
NX"
,"
EX"
,timeOut);

if(LOCK_SUCCESS.equals(var1)){
return true;

}
return false;

}

解锁代码:

Jedis jedis = new Jedis("
127.0.0.1"
, 6379);

private static final Long UNLOCK_SUCCESS = 1L;

/**
* 解锁操作
* @param key 锁标识
* @param value 客户端标识
* @return
*/

public static Boolean unLock(String key,String value){
String luaScript = "
if redis.call(\"
get\"
,KEYS[1]) == ARGV[1] then return
redis.call(\"
del\"
,KEYS[1]) else return 0 end"
;

Object var2 = jedis.eval(luaScript,Collections.singletonList(key), Collections.singletonList(value));

if (UNLOCK_SUCCESS == var2) {
return true;

}
return false;

}

luaScript 这个字符串是个lua脚本,代表的意思是如果根据key拿到的value跟传入的value相同就执行del,否则就返回0【保证安全性】

jedis.eval(String,list,list);
这个命令就是去执行lua脚本,KEYS的集合就是第二个参数,ARGV的集合就是第三参数【保证解锁的原子操作】

上述就实现了怎么使用redis去正确的实现分布式锁,但是有个小缺陷就是锁过期时间要设置为多少合适,这个其实还是需要去根据业务场景考量一下的。



分布式锁是分布式系统中常用的解决并发竞争问题的一种技术。其中Redis单节点和Redis集群都有很多实现方式。为了让大家更好地理解Redis加锁的方式,下面将主要介绍五种实现方式,希望对大家有所帮助。
一、 基于SETNX的方式
这种方式是Redis最简单易用的方式,通过SETNX命令来创建锁,如果返回值为1表示创建成功,0则表示创建失败。值得注意的是,这种方式需要手动释放锁。
二、 基于NXEX的方式
这种方式是在SETNX基础上改进的,由于SETNX需要手动释放锁,因此在使用时容易出现错误。为了避免这种情况,Redis提供了一个基于NXEX的命令,可以一条命令解决锁创建和过期时间设置的问题。
三、 基于RedLock的方式
这种方式是由Redis作者Antirez参与开发的,它是一个更加安全可靠的分布式锁实现方式。通过多个Redis节点协同工作,实现高可用、优先级控制等功能。但是,需要注意的是RedLock并不能百分百保证数据的一致性。
四、 基于Lua脚本的方式
这种方式是结合Redis的Lua脚本执行技术所实现的。将加锁逻辑封装在Lua脚本中,在执行脚本时,Redis会通过原子性保证加锁的正确性。该方式不需要手动释放锁。
五、 基于zookeeper的方式
这种方式主要是在分布式系统中使用,通过利用zookeeper来实现锁的创建和释放。在使用时需要保证zookeeper的稳定性和可靠性。同时,由于zookeeper进行锁的管理需要大量的网络交互,因此性能可能有所下降。
总结
正如开头所说,不同的实现方式具有不同的特点和适用场景。在使用分布式锁时,需要综合考虑因素,例如性能、安全性、复杂度等等,选择最适合自己的方式。