欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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天生就是单线程的,在高并发环境主从集群还是会存在一定问题。