多台tomcat服务器上实现分布式锁解决redis缓存击穿
1 分布式锁存放的位置
在“单台tomcat上利用锁解决缓存击穿”的代码中,锁只是加在了单台tomcat服务器上,实际开发时,tomcat服务器作为集群,分别搭载在不同的服务器上,为了能实现锁的共享,就需要使用分布式锁——多个tomcat,拿到同一把锁,而这把锁就放在redis中

2 分布式锁的原理 set命令的NX和XX
分布式锁放在redis中,依据的原理就是redis的set 命令的NX和XX

NX:只有当key不存在时才能设置成功,返回nil代表设置不成功,NX的特点是写和读同时进行,这就契合锁的概念——设置并立马返回这个锁(整个过程是一步操作)

XX:只有当key存在时才能设置成功,返回nil代表设置不成功

基于NX和XX的特点,以及redis的特性——单线程+原子性,使用redis的这两个特性制作自定义的分布式锁就很安全
3 手动创建分布式锁
技术点:
-
JAVA代码中,通过stringRedisTemplate类实现set NX的方法是stringRedisTemplate.opsForValue().setIfAbsent()
-
业务执行完毕后,使用stringRedisTemplate.delete()方法删除创建的锁
-
要给生成的锁设置过期时间,避免代码执行到一半服务器断电,导致代码只执行一半,最后锁没能得到删除
-
加锁后,就要让其他没有获取到锁的进程睡眠等待一段时间,过段时间再请求获取锁,这个过程叫自旋
-
因为有过期时间的设定,如果方法执行时间过长,就会导致原有的锁已经过期消失,如果正巧有别的实例拿到了一把叫“lock”的同名锁,当前方法执行删除操作时就会把别的实例的锁删除
所以要判断当前即将被删除的锁是属于当前方法而非其他实例,要实现这个功能,可以在每次生成“lock”锁时,生成一个随机数存入value,在删除前对比锁的value是否与创建锁时的随机数一致,一致才表示拿到的是自己的锁而非别人的锁
public class UserServiceImpl implements UserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
public boolean getLock(){
String uuid = IdUtil.randomUUID(); //生成随机数 用于给创建的锁打上标识——这个随机数设为锁的value值
//使用set nx的特性来实现分布式锁,为了防止不可避免的情况产生,如断电,死机等,需要给锁设置一个过期时间,让锁能够超时自动释放
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,15,TimeUnit.SECONDS);
try {
if(lock){
try {
//拿到锁后,该干啥干啥——查询redis 有就返回 没有就掉dao层方法,获得结果后除了返回,还得存入redis中
} finally {
String lock1 = stringRedisTemplate.opsForValue().get("lock"); //获取锁的值
if(lock1.equals(uuid)){ //获取的锁的值必须与当前的锁的值一致,防止本方法还在执行,锁已经超时释放,删掉了其他实例的锁
stringRedisTemplate.delete("lock");
}
}
}else{
Thread.sleep(1000); //没能得到锁就睡眠一段时间
getLock(); //自旋操作
}
} catch (Exception e) {
e.printStackTrace();
}
return lock;
}
}

浙公网安备 33010602011771号