前言
在使用Redis的过程中遇到的一些问题记录。
set命令的其他参数
SET命令可用选项的基本语法
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
可选参数如下:
- ex : 到期时间(以秒为单位)
- px : 到期时间(以毫秒为单位)
- nx : 仅在键不存在时设置键
- xx : 只有在键已存在时才设置
示例
SET mykey “redis” EX 60 NX
在键“mykey”不存在时,设置键的值为“redis”,到期时间为60秒。
setnx概念
setnx是「SET if Not eXists」的缩写,只有不存在的时候才设置,可以利用它来实现锁的效果。
SETNX key value
若给定的 key 已经存在,则 setnx不做任何动作。
使用redisTemplate操作setnx
@Override
public boolean setNx(String key,String value, long time) {
try {
RedisCallback<String> callback = (connection) -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
return commands.set(key, value, "NX", "PX", time);
};
String result = redisTemplate.execute(callback);
return !StringUtils.isEmpty(result);
} catch (Exception e) {
logger.error("set redis occured an exception", e);
}
return false;
}
作用
分布式锁
向Redis添加一个key,添加成功代表获取到锁,添加失败则代表没有获取到锁。只要保证多个线程使用的是同一个key,它们添加时就只会有一个线程添加成功获取到锁。而释放锁时只需要将锁删除即可。
可能遇到的问题
死锁
如果前一个线程由于某种原因获取到锁后未正常释放,后面的线程获取不到锁无法往下继续执行,就会形成死锁的现象。
解决方案
设置锁的过期时间。
由上面提到的EX
或者PX
即可设置setnx
的key
的过期时间。
误删锁
在设置了过期时间后,可能会出现这样的现象:
线程1在自己的锁过期后才从沉睡中苏醒,且想要执行释放锁操作。而此时实际的锁归线程2所有,因此线程1释放的锁实际是线程2的锁,且线程2并不知情。
当线程2想释放锁时,线程3可能已经获取到了被线程1释放的锁,导致线程2释放的锁实际是线程3的锁。
以此类推每一个线程释放的都是下一个线程的锁,直到最后一个线程无锁可删。
这种误删锁的情况让锁的存在意义荡然无存,本来应该串行执行的线程,在一定程度上都开始并发执行了。
解决方案
给锁加上线程标识。
在添加key的时候,key的value存储当前线程的唯一标识。在删除锁的时候,将线程标识取出来进行判断,如果相同就表示锁是自己的能够删除,否则不能删除。
判断锁能否删除的操作和删除锁的操作要作为一个整体执行,保证锁删除的原子性,避免线程1判断锁后锁被线程2获取,仍然会导致线程1误删线程2的锁的问题。