分布式锁
原理与使用
我们可以在redis命令行通过setnx命令来完成分布式锁,加上NX后只有当key不存在时才会set
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set a b NX
(nil)
将分布式锁的简单逻辑转换为java业务代码如下
public void testLock(){
// java代码中使用setIfAbsent()方法代理redis命令行中的setNX
Boolean lock = redisTemplate.opsForValue.setIfAbsent("lock","111");
if(lock){
// 加锁成功 执行业务方法
function();
// 执行完成后,释放锁
redisTemplate.delete("lock");
} else {
// 加锁失败,每隔一段时间后重试
Thread.sleep(500);
// 使用自旋的方式 重试
testLock()
}
}
问题:没有删除锁逻辑
可能在执行业务方法时出现了异常,或者是执行过程中宕机了,没有删除锁,这就会造成死锁
解决方法是:设置锁的自动过期时间,如果没有删除就自动删除。
public void testLock(){
Boolean lock = redisTemplate.opsForValue.setIfAbsent("lock","111");
if(lock){
// 设置自动过期时间 30秒
redisTemplate.expire("lock", 30, TimeUnit.SECONDS)
function("业务方法");
redisTemplate.delete("lock");
} else {
Thread.sleep(500);
testLock()
}
}
问题:现在往redis中存值和设置过期时间不是原子操作,有可能在if判断执行完后服务器断电或者宕机,那就还是会造成锁一直存在。
解决方法是:存值和设置过期时间变为原子操作,要么都成功,要么都不成功。
# redis命令行中 set命令的格式如下,其中可以加EX和PX都是设置过期时间,EX单位是秒 PX单位是毫秒
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
# 如下所示
127.0.0.1:6379> set a aa EX 30 NX
现在java代码中的写法如下,在setIfAbsent()方法中还传入过期时间与过期单位
public void testLock(){
// 设置自动过期时间 30秒
Boolean lock = redisTemplate.opsForValue.setIfAbsent("lock","111", 30, TimeUnit.SECONDS);
if(lock){
function("业务方法");
redisTemplate.delete("lock");
} else {
Thread.sleep(500);
testLock()
}
}
问题:可能业务方法执行耗时较长,锁自己过期了,我们直接删除,可能把别人正在持有的锁删除了
解决方法是:value之前是随便写的一个值,现在不随便写了,先生成一个uuid作为锁的值存入redis,删除锁之前先查询判断redis中存储的uuid和自己生成的uuid相同后才去删除锁。
public void testLock(){
// 生成uuid
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue.setIfAbsent("lock",uuid, 30, TimeUnit.SECONDS);
if(lock){
function("业务方法");
// 先判断uuid是否相同 再删除
String uuidRedis = redisTemplate.opsForValue.get("lock");
if(uuid