Redis实现分布式锁
分布式锁要求
- 排他性:同一时间只能有一个客户端获取到锁,其他客户端无法同时获取到锁。
- 避免死锁:这把锁在一段有限的时间之后,一定要被释放,否则产生死锁,这里包括正常释放和非正常释放;
- 自己解锁:加锁和解锁都应该是同一客户端去完成,不能去解别人的锁。
- 高可用:获取和释放的机制必须高可用且保证性能。
Redis分布式锁实现步骤
加锁
setnx
命令加锁,并设置锁的有效时间和持有人标识。 setnx
:只有在key
不存在的情况下,将key
的值设置位value
。如果key
存在,则setnx
命令不做任何动作。 expore
命令设置锁的过期时间。
解锁
del
命令删除锁
加锁代码
java
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//锁前缀
private static final String REDIS_LOCK_PRIFIX = "redis:lock:";
/**
* 加锁
* @param lockName - 锁名称,不同业务可以有不同的锁名称来区分
* @param acquireLockTimeOut(毫秒) - 获取锁的超时时间,超过该时间不再获取
* @param lockExpireTimeOut(毫秒) - 释放锁的超时时间,超过该时间释放锁
* @return - 返回存入的value值
*/
public String acquireLock(String lockName,long acquireLockTimeOut,long lockExpireTimeOut){
//key 和 value
String redisLockKey = REDIS_LOCK_PRIFIX + lockName;
String redisLockValue = UUID.randomUUID().toString();
//获取redis连接,此处springboot配置好可略过
//此处省略:redis.getConn
//当前时间:20:00:00 获取锁超时时间:5000毫秒,那么20:00:05则超时
long acquireLockEndTime = System.currentTimeMillis() + acquireLockTimeOut;
while (System.currentTimeMillis() < acquireLockEndTime){
//去redis获取锁 - (setnx + expire)这两个命令需要保证原子操作,此时需要使用lru脚本来实现
//lua脚本,使setnx和expire为原子操作
String scriptStr = "if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then\n" +
" return redis.call('expire', KEYS[1], ARGV[2]));\n" +
"else\n" +
" return 0;\n" +
"end";
//lua脚本字符串
RedisScript script = RedisScript.of(scriptStr);
//参数list
List<String> redisParamList = new ArrayList<>();
redisParamList.add(redisLockKey);
//执行lua脚本 - 毫秒换算 /1000 = 秒
Object result = redisTemplate.execute(script, new StringRedisSerializer(), new StringRedisSerializer(), redisParamList, redisLockValue, lockExpireTimeOut/1000);
// 1代表执行成功,代表拿到了分布式锁
if(result.toString().equals("1")){
//将值直接return
return redisLockValue;
}
//循环去redis获取锁,休眠一会再去获取
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没拿到锁,返回null
return null;
}
解锁代码
java
/**
* 释放锁
* @param lockName - 锁名称,不同业务可以有不同的锁名称来区分
* @param redisLockValue - 锁对应的value值
*/
public void releaseLock(String lockName, String redisLockValue){
String redisLockKey = REDIS_LOCK_PRIFIX + lockName;
//删除key,需要判断,是不是当前锁的key(自己加锁,自己解锁。不能去解别人的锁) -> 先查询,再删除(需要保证原子性)
String scriptStr = "if (redis.call('get',KEYS[1]) == ARGV[1]) then\n" +
" return redis.call('del',KEYS[1]);\n" +
"else\n" +
" return 0;\n" +
"end";
//lua脚本字符串
RedisScript script = RedisScript.of(scriptStr);
//参数list
List<String> redisParamList = new ArrayList<>();
redisParamList.add(redisLockKey);
//执行lua脚本 - 释放锁
Object result = redisTemplate.execute(script, new StringRedisSerializer(), new StringRedisSerializer(), redisParamList, redisLockValue);
}