zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Redisson分布式锁实战(适用于Redis高并发场景)

Redis并发分布式分布式 实战 场景 适用 Redisson
2023-09-14 09:02:04 时间

实现方式一:存在抛异常后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天生就是单线程的,在高并发环境主从集群还是会存在一定问题。