Redisson分布式锁实战(适用于Redis高并发场景)
程序员文章站
2022-06-19 10:57:22
...
实现方式一:存在抛异常后lock值无法归0的问题
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("deduct_stock")
public void deductStock(){
Long num = stringRedisTemplate.opsForValue().increment("lock", 1);
//多个线程过来 只有一个线程会将num值设置为1 其余的线程都不可能为1
if (num==1){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
stringRedisTemplate.opsForValue().set("stock",(stock-1)+"");
System.out.println("扣减成功,库存stock:"+(stock-1)+"");
}else{
System.out.println("扣减失败,库存不足");
}
}
//还原
stringRedisTemplate.opsForValue().increment("lock",-1);
}
实现方式二:加try…finally。无论程序是否抛出异常,最终都会还原。但是在还原为0的时候有可能redis挂了,或者是程序还没执行完try代码块中的内容,整个web应用挂掉了,finally块中的内容也无法执行。即使程序重启,后面的线程也始终无法满足num==1的条件。
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("deduct_stock")
public void deductStock(){
try{
Long num = stringRedisTemplate.opsForValue().increment("lock", 1);
//多个线程过来 只有一个线程会将num值设置为1 其余的线程都不可能为1
if (num==1){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
stringRedisTemplate.opsForValue().set("stock",(stock-1)+"");
System.out.println("扣减成功,库存stock:"+(stock-1)+"");
}else{
System.out.println("扣减失败,库存不足");
}
}
}finally {
//还原
stringRedisTemplate.opsForValue().increment("lock",-1);
}
}
实现方式三:设置lock的超时时间。存在这么一个场景,由于某一段时间内,网络环境差,导致本应10秒内就执行完的程序执行了15秒才完成,而锁已经过期了,也就意味着后面的线程能拿到锁,导致锁形同虚设。
还存在这么一个情况:第一个线程执行时长15秒,假设第二个线程执行时长8秒,由于锁已失效,第二个线程是能重新拿到新锁的,结果第一个线程在执行归0操作释放锁时,它自己的锁已失效,导致释放的并不是自己的锁,而是第二个线程的锁。总之,存在一系列场景导致锁失效。
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("deduct_stock")
public void deductStock(){
try{
String lockkey = "lock";
Long num = stringRedisTemplate.opsForValue().increment(lockkey, 1);
//给key设置超时时间 到期自动清理
stringRedisTemplate.expire(lockkey,10, TimeUnit.SECONDS);
//多个线程过来 只有一个线程会将num值设置为1 其余的线程都不可能为1
if (num==1){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock>0){
stringRedisTemplate.opsForValue().set("stock",(stock-1)+"");
System.out.println("扣减成功,库存stock:"+(stock-1)+"");
}else{
System.out.println("扣减失败,库存不足");
}
}
}finally {
//还原
stringRedisTemplate.opsForValue().increment("lock",-1);
}
}
这种场景下,可以开启一个分线程,与当前线程绑定,如果锁的失效时间是10秒,那么就每隔5秒去扫描一下这把锁,看看锁是否还在,如果还在就再次将失效时间重置为10秒,不断延时,也就相当于让这把锁具备了自动延时的功能,直到当前线程亲自将这把锁释放,否则一致延时下去。
Redisson框架实现分布式锁
上述分析也就是Redisson的实现原理,只不过会增加一个线程,让等待的线程不断地尝试加锁,通过while循环来实现的,俗称就是自旋加锁。
配置单机Redis
/**
* @ProjectName springbootdemo_src
* @ClassName RedissionConfig
* @Desicription TODO
* @Author Zhang Xueliang
* @Date 2019/7/27 17:34
* @Version 1.0
**/
@Configuration
public class RedissonConfig {
@Bean("redisson")//如果不写value 默认就会以方法名作为bean的名称进行注入
public Redisson getRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
加锁代码:很简单,就三行代码。重要的是理解运行原理。
/**
* @ProjectName springbootdemo_src
* @ClassName RedissionController
* @Desicription TODO
* @Author Zhang Xueliang
* @Date 2019/7/27 15:55
* @Version 1.0
**/
@SuppressWarnings("all")
@RestController
public class RedissionController {
@Autowired
private Redisson redisson;
@RequestMapping("redisson_lock")
public void redissonDeductStock() {
String lockkey = "lock";
RLock lock = redisson.getLock(lockkey);
try {
lock.lock();//如果使用默认的午餐lock方法 默认超时时间设置的是30秒 可以传入自定义的超时时间
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
stringRedisTemplate.opsForValue().set("stock", (stock - 1) + "");
System.out.println("扣减成功,库存stock:" + (stock - 1) + "");
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
lock.unlock();
}
}
}
Redisson实质上就是给代码加上了一把悲观锁,而悲观锁是比较影响性能的。increment自增加1的方式实质是乐观锁。
Redis天生就是单线程的,在高并发环境主从集群还是会存在一定问题。