基于redis实现分布式锁

107 阅读3分钟

redis实现分布式锁

为什么要用分布式锁?

在平常开发业务中,肯定遇到过对共享资源多线程访问,这时候就需要加锁保证业务的正确性。单机环境下这样做没问题,因为多线程都能获取到jvm中的锁(单机环境)。随着业务发展,机器开始做集群,分布式部署保证高可用,那么问题来了,多个jvm之间是不能通讯的,那么按照前面说的加锁就有问题,因为每个jvm的锁是只存在自及的jvm中。要解决这个问题,需要每台机器都能获取到同一把锁也就是分布式锁。

redis实现分布式锁

举个秒杀活动列子:用户抢购商品,获取到库存,检查库存,如果库存不足返回,库存充足,处理扣除逻辑。

 public Boolean purchase(){

    Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get("commodity"));
    if(count <=0){
        System.out.println("商品已卖完");
        return false;
    }

    System.out.println("购买成功,处理逻辑");
    stringRedisTemplate.opsForValue().set("commodity",String.valueOf(count-1));

    return true;
}

这段代码有什么问题呢?在并发量不高的情况下,是能满足需求的,但如果并发量高的话,就会存在超卖的现象。 原因分析:在并发情况下,线程1先获取到库存,发现库存充足,执行扣除逻辑,在线程1还没有执行到删库存的时候,线程2也获取库存,这时候线程2获取到和线程1还没扣减的库存,这就导致了超卖。解决思路:就是加锁

tips: Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

上面代码修改为:



public Boolean purchase(){
        String lockName ="lock";
        //获取锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, "lock");
        if(!lock){
            System.out.println("获取锁失败");
            return false;
        }
        try {
            Integer count=Integer.parseInt(stringRedisTemplate.opsForValue().get("commodity"));
            if (count <= 0) {
                System.out.println("商品已卖完");
                return false;
            }
            System.out.println("购买成功,处理逻辑");
            stringRedisTemplate.opsForValue().set("commodity", String.valueOf(count - 1));
        }catch (Exception e){
            return false;
        }finally {
            //释放锁
            stringRedisTemplate.delete(lockName);
        }
        return true;
    } 

分析:修改后的代码有什么问题呢?如果一个线程获取到锁后,以为一些其他原因,比如:比如断电,服务器重启了导致没有执行释放锁操作,其他线程也一直获取不到锁,那么这把锁会变成死锁。解决思路:给key加一个过期时间

tips: SETNX key <current Unix time + lock timeout + 1> 命令在指定的 key 不存在时,为 key 设置指定的值和过期时间

   public Boolean purchase(){
        String lockName ="lock";
        //获取锁,加上过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, "lock",10, TimeUnit.SECONDS);
        if(!lock){
            System.out.println("获取锁失败");
            return false;
        }
        try {
            Integer count=Integer.parseInt(stringRedisTemplate.opsForValue().get("commodity"));
            if (count <= 0) {
                System.out.println("商品已卖完");
                return false;
            }
            System.out.println("购买成功,处理逻辑");
            stringRedisTemplate.opsForValue().set("commodity", String.valueOf(count - 1));
        }catch (Exception e){
            return false;
        }finally {
            //释放锁
            stringRedisTemplate.delete(lockName);
        }
        return true;
    }

分析:上面代码有什么问题呢?假如线程1获取到了锁,执行处理逻辑的时间超过了10秒,redis主动吧key过期了,线程2过来获取到了锁,线程2开始获取库存执行执行操作,执行过程中,线程1开始执行了删除key操作,然后线程3获取了锁也开始获取库存执行操作,这样也要导致超卖的问题。解决思路:吧当前线程设置一个Id设置到锁的值中,让线程只能删除线程自己获取的锁。

 public Boolean purchase(){

        String uuid = UUID.randomUUID().toString();
        String lockName ="lock";
        //获取锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, uuid,10, TimeUnit.SECONDS);
        if(!lock){
            System.out.println("获取锁失败");
            return false;
        }
        try {
            Integer count=Integer.parseInt(stringRedisTemplate.opsForValue().get("commodity"));
            if (count <= 0) {
                System.out.println("商品已卖完");
                return false;
            }
            System.out.println("购买成功,处理逻辑");
            stringRedisTemplate.opsForValue().set("commodity", String.valueOf(count - 1));
        }catch (Exception e){
            return false;
        }finally {
            //释放锁
            if(uuid.equals(stringRedisTemplate.opsForValue().get(lockName))) {
                stringRedisTemplate.delete(lockName);
            }
        }
        return true;
    }

代码写到现在这个位置就差不多了,但锁的过期时间到底设置多少秒?设置大了,如果服务器宕机,重启服务器后锁很久才过期,设置小了怕因为其他以为导致没有执行到释放锁操作。解决思路:在处理业务逻辑任务的时候,开一个新的线程,在新线程中每隔一段时间重新设置过期时间。一般过了设置过期时间的1/3后重新设置过期时间。

思考问题: 以上都是redis在单机的情况下,如redis做了主从,做了集群,从reids服务器还没有同步到主redis服务器的数据, 又会产生什么问题,怎么解决?