Redis分布式锁
redis1.0版本的锁(有bug版本的分布式锁)
@GetMapping("/Redis1/{id}")
public String RedisLock1(@PathVariable("id") String id){
String LockName="LockName";
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(LockName, "shuxiaosheng",10,TimeUnit.SECONDS);
if (lock!=null&&!lock){
return "系统繁忙,请稍微再试";
}
try {
Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
Integer realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock.toString());
}
}finally{
stringRedisTemplate.delete(LockName);
}
return "end";
}
模拟因处理业务时间过长而导致的分布式锁失效的情况
@GetMapping("/Redis1/{id}")
public String RedisLock1Exception(@PathVariable("id") Integer id) throws Exception{
String LockName="LockName";
Boolean shuxiaosheng = stringRedisTemplate.opsForValue().setIfAbsent(LockName, "线程"+id,10,TimeUnit.SECONDS);
if (shuxiaosheng!=null&&!shuxiaosheng){
return "系统繁忙,请稍微再试";
}
try {
//线程暂停超过十秒,锁就自动释放了
if(1==id){
Thread.sleep(15000);
System.out.println("线程1暂停15秒,锁已经自动释放");
}
if(2==id){
Thread.sleep(8000);
System.out.println("线程2暂停8秒");
}
Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
Integer realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock.toString());
}
}finally{
System.out.println("当前线程是:"+id+"释放的锁是:"+stringRedisTemplate.opsForValue().get(LockName));
stringRedisTemplate.delete(LockName);
}
return "end";
}
1.0版本的存在的缺陷,例如:
- 当线程1进来,执行业务代码需要15秒.
- 当线程2进来,执行业务代码需要8秒.
- 线程1最先进来,拿到了锁,当线程1执行业务代码到11秒时,锁被自动释放了.
- 线程2立马进来,拿到了锁,当线程2执行业务代码到4秒时,这时,线程1业务代码已经执行结束,并且手动释放了线程2加的锁,
控制台打印
线程1暂停15秒,锁已经自动释放
当前线程是:1释放的锁是:线程2
线程2暂停8秒
当前线程是:2释放的锁是:null
总结:
因为高并发,线程1执行业务的代码时间大于锁超时时间,线程1的锁自动释放了,线程2加入进来,结果线程1执行完毕,释放了线程2加的锁,出现问题
redis2.0版本的锁(适用于并发一般的软件公司)
@GetMapping("/Redis2/{id}")
public String RedisLock12(@PathVariable("id") Integer id) throws Exception{
String LockName="LockName";
String uuid = UUID.randomUUID().toString();
Boolean shuxiaosheng = stringRedisTemplate.opsForValue().setIfAbsent(LockName, uuid+"线程"+id,10,TimeUnit.SECONDS);
if (shuxiaosheng!=null&&!shuxiaosheng){
return "系统繁忙,请稍微再试";
}
try {
//线程暂停十秒,锁就自动释放了
if(1==id){
Thread.sleep(15000);
System.out.println("线程1暂停15秒,锁已经自动释放");
}
if(2==id){
Thread.sleep(8000);
System.out.println("线程2暂停8秒");
}
Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
Integer realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock.toString());
}
}finally{
//当前线程只能释放自己加的锁
if ((uuid+"线程"+id).equals(stringRedisTemplate.opsForValue().get(LockName))){
System.out.println("当前线程是:"+id+"释放的锁是:"+stringRedisTemplate.opsForValue().get(LockName));
stringRedisTemplate.delete(LockName);
}
}
return "end";
}
控制台打印
线程1暂停15秒,锁已经自动释放
线程2暂停8秒
当前线程是:2释放的锁是:c579e42a-ecfb-40a4-8e5f-689f3d858f40线程2
总结
redis2.0相比较与redis1.0,就加了个核心判断,当前线程只能释放自己加的锁,不能释放别人的锁
到此简单的redis锁就已经实现了
redis3.0版本的锁(适用并发比较高互联网公司)
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.1</version>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
添加配合类
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod="shutdown")
RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
config.useSingleServer()
.setAddress("127.0.0.1:6379");
return Redisson.create(config);
}
}
实现
@RequestMapping("/redisson")
public String testRedisson(){
//获取分布式锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("lock");
//加锁(阻塞等待),默认过期时间是30秒
lock.lock();
try{
//如果业务执行过长,Redisson会自动给锁续期
Thread.sleep(1000);
System.out.println("加锁成功,执行业务逻辑");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁,如果业务执行完成,就不会继续续期,即使没有手动释放锁,在30秒过后,也会释放锁
lock.unlock();
}
return "Hello Redisson!";
}
原理
大家都知道,如果刚上完锁,负责存储这个分布式的Redisson节点宕机后,这样就出现了死锁,为了避免这种情况发生,Redisson内部提供了一个监控锁的看门狗,他的作用是:如果当前线程还有持有锁,且锁快要到期了(默认锁是30秒),就继续延长锁的时间,如果当前线程挂了,那么就不会自动延长锁,到期之后,锁会自动释放