redis分布式锁是为了解决什么问题?
为了解决synchronized在分布式情况下无法实现同步的问题,因为synchronized同步的是一个jvm的方法,多个jvm的话是做不到的。
1. 实现redis 基本业务减库存场景
Integer stock = (Integer)redisTemplate.opsForValue().get("stock");
log.info("还剩"+stock);
if (stock<=0){
log.info("抢购失败");
}
redisTemplate.opsForValue().set("stock",stock-1);
这段代码实现了基本业务场景,当有并发时会发生多个线程获取的stock是一样的,从而导致减1操作重复,导致超卖
2.实现redis分布式锁的基本功能
String key = "lockKey";
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1"); //setnx key 1
//如果加锁失败,说明已经其他线程已经加上锁了
if (!flag){
log.info("系统异常,请稍后再试");
return;
}
try{
Integer stock = (Integer)redisTemplate.opsForValue().get("stock"); //get key
if (stock<=0){
log.info("抢购失败");
return;
}
Long stock1 = redisTemplate.opsForValue().decrement("stock"); //decr key
log.info("还剩"+stock1);
}finally {
//防止代码错误导致没有删除分布式锁
redisTemplate.delete(key); //del key
}
这段代码解决了超卖问题,但是如果在业务处理过程中,应用宕机了怎么办,这个锁就锁死了。
3.应用宕机怎么办?
解决方案给redis key加上过期时间,就算宕机了过一段时间redis也会释放分布式锁
String key = "lockKey";
//写法1
// redisTemplate.opsForValue().setIfAbsent(key, "1"); //setnx key 1
// redisTemplate.expire(key,10,TimeUnit.SECONDS); //expire key 10
//写法2
//一条redis命令具有原子性,优于上面这种写法
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key,"1",10, TimeUnit.SECONDS); //setnx key 1 10
//如果加锁失败,说明其他线程已经加上锁了
if (!flag){
log.info("系统异常,请稍后再试");
return;
}
try{
Integer stock = (Integer)redisTemplate.opsForValue().get("stock"); //get key
if (stock<=0){
log.info("抢购失败");
return;
}
Long stock1 = redisTemplate.opsForValue().decrement("stock"); //decr key
log.info("还剩"+stock1);
}finally {
//防止代码错误导致没有删除分布式锁
redisTemplate.delete(key); //del key
}
这时会出现一个新的问题,线程A还没走完业务锁过期释放了,这时线程B会加一把新锁,而线程A走完业务后会释放线程B的锁,这个锁加的就有问题了。
一个解决方案就是锁加久一点,并且只允许删除自己线程加的锁,这完全适用了普通业务场景,但是如果是高并发情况下,比如设置了1分钟过期时间,但加锁后系统宕机了,那么这一分钟这个方法是锁死的,对于系统来说是极其致命的。
高并发场景下设置多久的过期时间都存在问题,这时就需要加一个锁续命功能。当锁到期还没执行完业务的情况下,再给锁设置一个过期时间,直到当前线程业务同步方法执行完毕。
4.使用redisson来实现该功能
该框架已经替我们解决了这些事情,这里只简单使用一下,具体用法还是去官网看看。
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
配置类:
@Bean
public Redisson redisson() {
Config config = new Config();
// 此为单机模式
config.useSingleServer().setAddress("redis://127.0.0.1:6379s").setDatabase(0);
return (Redisson) Redisson.create(config);
}
同步代码:
String key = "lockKey";
//获取redisson锁
RLock lock = redisson.getLock(key);
try{
//加锁 默认为非公平锁 加锁时间30s 会自动续命
lock.lock();
Integer stock = (Integer)redisTemplate.opsForValue().get("stock"); //get key
if (stock<=0){
log.info("抢购失败");
return;
}
Long stock1 = redisTemplate.opsForValue().decrement("stock"); //decr key
log.info("还剩"+stock1);
}finally {
lock.unlock();
}
这样就使用redis分布式锁解决了并发问题。