怎么来实现这个分布式锁呢?
方案一:
这种设计方案,会出现一个问题:当线程获取到锁,然后执行完业务逻辑,准备去删除锁的时候,突然服务器宕机了,会导致这个锁一直存在,得不到释放,会造成死锁的情况。
解决方案就是:设置一个过期时间,即使服务器宕机不能手动释放,也可以过期自动释放
方案二:
解决了方案一的问题,不过,还会有问题,假如当我们获取到锁之后将要去设置过期时间的时候,这时候服务器宕机了,也会造成死锁情况。
解决方案:保证获取锁和设置过期时间是原子性的,setnx ex命令可以保证原子性
方案三:
这种方案解决了设置锁的原子性,但是在删除锁的时候,是应该直接删除的吗?当我们的业务执行时间很长的时候,这时候假定锁已经过期了,别的线程获得了锁,先前线程执行完业务之后,去删除锁,就会去删除别人的锁
解决方案:在设置锁的时候指定自己的UUID,执行完业务后,获取锁检查是否是自己之前设置的,如果是自己设置的,就删除,否则就跳过,在删除锁的时候也要保证原子性,为什么呢?假如我们获取到这个锁的确是我们自己之前设置的,但是在获取值到删除锁中间还是有一段时间,假如这段时间,锁失效了,别人获取到了锁,这时候我们还是会认为锁是自己的,会导致误删。
方案四:
手动如何实现分布式锁
实现分布式锁的前提一定要保证在获取到锁+过期时间、获取锁+删除锁这两步操作都是原子操作。
上图就是我解决方案的流程图。接下来就看看我代码是怎么实现的吧。
/**
* 从数据库查询并封装数据::分布式锁
* @return
*/
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1、占分布式锁。去redis占坑 设置过期时间必须和加锁是同步的,保证原子性(避免死锁)
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功...");
Map<String, List<Catelog2Vo>> dataFromDb = null;
try {
//加锁成功...执行业务(只允许获取到分布式锁的线程去数据库中查)
dataFromDb = getDataFromDb();
} finally {
// Lua脚本,在脚本中有两步操作:一、获取当前这个分布式锁,判断这个分布式锁是不是我的,二、如果是我的就删除,并返回1,否则返回0
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//删除锁
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
//先去redis查询下保证当前的锁是自己的
//获取值对比,对比成功删除=原子性 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)) {
// //删除我自己的锁
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
} else {
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDbWithRedisLock(); //自旋的方式
}
}