本文集体博客:https://abeille.top/blog/detail/AT811U9QO

应用场景:

抢购流动,限量供应;

首先第一步设计:将库存信息放入redis进行缓存;

public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods(){        // 获取key对应的数据        Integer stock = redisTemplate.opsForValue().get("stock");        // 如果库存大于0,对库存减1        if(null != stock && stock > 0){            int realStock = stock - 1;            // 将批改后的库存放入redis            redisTemplate.opsForValue().set("stock", realStock);            System.out.println("库存扣减胜利,以后库存为:" + realStock);        } else {            System.out.println("库存扣减失败");        }    }}

问题一:高并发场景下会呈现库存扣减异样;

以上代码存在一个问题:当高并发场景下,会有多个申请同时获取到同样的数据,而后进行操作,实际上操作了屡次,然而库存只减了一次;
那这样的场景的解决方案有:

  1. 加synchronized锁,示例如下:
public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods(){           // 加同步锁        synchronized (this){            // 获取key对应的数据            Integer stock = redisTemplate.opsForValue().get("stock");            // 如果库存大于0,对库存减1            if(null != stock && stock > 0){                int realStock = stock - 1;                // 将批改后的库存放入redis                redisTemplate.opsForValue().set("stock", realStock);                System.out.println("库存扣减胜利,以后库存为:" + realStock);            } else {                System.out.println("库存扣减失败");            }        }    }}

问题二:多实例部署,synchronized锁生效的问题;

当服务为单机的状况下,加synchronized是能够解决问题的,然而如果多实例部署,那么这个锁就没有成果了;针对新产生的这个问题,能够通过应用redis.setnx()办法来解决;应用redistemplate,操作的是setIfAbsent()办法:

public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods() {//        synchronized (this){        String lockKey = "stockLock";        // 针对多实例部署,能够应用setIfAbsent()办法,这个代码理论是redis.setNx()办法        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119);        if (null == aBoolean || !aBoolean) {            return;        }        // 获取key对应的数据        Integer stock = redisTemplate.opsForValue().get("stock");        // 如果库存大于0,对库存减1        if (null != stock && stock > 0) {            int realStock = stock - 1;            // 将批改后的库存放入redis            redisTemplate.opsForValue().set("stock", realStock);            System.out.println("库存扣减胜利,以后库存为:" + realStock);        } else {            System.out.println("库存扣减失败");        }          // 操作实现之后,删除锁        redisTemplate.delete(lockKey);//        }    }}

问题三:当办法执行过程中代码执行出现异常,锁无奈删除的问题:

当办法执行时,如果两头某一步执行产生异样,那么前面的代码是无奈执行到的,那也就是说,redistemplate.delete()是执行不到的;这时候的解决方案是应用try-finally或者try-with-resource来解决,代码示例如下;

public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods() {//        synchronized (this){        String lockKey = "stockLock";        try {            // 针对多实例部署,能够应用setIfAbsent()办法,这个代码理论是redis.setNx()办法            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119);            if (null == aBoolean || !aBoolean) {                return;            }            // 获取key对应的数据            Integer stock = redisTemplate.opsForValue().get("stock");            // 如果库存大于0,对库存减1            if (null != stock && stock > 0) {                int realStock = stock - 1;                // 将批改后的库存放入redis                redisTemplate.opsForValue().set("stock", realStock);                System.out.println("库存扣减胜利,以后库存为:" + realStock);            } else {                System.out.println("库存扣减失败");            }            //        }        } finally {            // 操作实现之后,删除锁            redisTemplate.delete(lockKey);        }    }}

问题四:如果办法执行过程中,服务整个挂掉了,那么加的锁会始终存在的问题;

当办法执行过程中,服务挂掉了,或者重启了,那没有开释掉的锁会始终存在,解决方案是给这个锁设置一个生效工夫;

设置生效办法有两种:

  1. 先设置锁,在自行设置生效工夫;
  2. 在设置锁的同时(并非真正同时,而是redis本人实现的原子操作)设置生效工夫;
public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods() {//        synchronized (this){        String lockKey = "stockLock";        try {            // 针对多实例部署,能够应用setIfAbsent()办法,这个代码理论是redis.setNx()办法            // 给锁设置生效工夫,办法有两种:一种是先设置锁,而后在自行设置生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119);//            redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS);            // 办法二:在设置锁的同时设置其生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此办法是对上面的额办法传入的工夫的一个封装            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, 15, TimeUnit.SECONDS);                        if (null == aBoolean || !aBoolean) {                return;            }            // 获取key对应的数据            Integer stock = redisTemplate.opsForValue().get("stock");            // 如果库存大于0,对库存减1            if (null != stock && stock > 0) {                int realStock = stock - 1;                // 将批改后的库存放入redis                redisTemplate.opsForValue().set("stock", realStock);                System.out.println("库存扣减胜利,以后库存为:" + realStock);            } else {                System.out.println("库存扣减失败");            }//        }        } finally {            // 操作实现之后,删除锁            redisTemplate.delete(lockKey);        }    }}

问题五:当办法还没执行完,锁生效了的问题;

当服务多实例部署是,因为网络,服务器等起因,办法执行工夫不等,可能存在在开释锁的时候,这个锁曾经生效的状况,或者开释的锁不是本次操作增加的锁,那么这个锁也就生效了;解决办法是在加锁时,增加一个身份标识,在开释锁时,判断这个锁是否本人增加的,示例代码如下:

public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    private void buyGoods() {//        synchronized (this){        String lockKey = "stockLock";        int nextInt = 0;        try {            nextInt = new Random().nextInt(100);            // 针对多实例部署,能够应用setIfAbsent()办法,这个代码理论是redis.setNx()办法            // 给锁设置生效工夫,办法有两种:一种是先设置锁,而后在自行设置生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119);//            redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS);            // 办法二:在设置锁的同时设置其生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此办法是对上面的额办法传入的工夫的一个封装            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, nextInt, 15, TimeUnit.SECONDS);            if (null == aBoolean || !aBoolean) {                return;            }            // 获取key对应的数据            Integer stock = redisTemplate.opsForValue().get("stock");            // 如果库存大于0,对库存减1            if (null != stock && stock > 0) {                int realStock = stock - 1;                // 将批改后的库存放入redis                redisTemplate.opsForValue().set("stock", realStock);                System.out.println("库存扣减胜利,以后库存为:" + realStock);            } else {                System.out.println("库存扣减失败");            }//        }        } finally {            // 操作实现之后,删除锁            Integer lockValue = redisTemplate.opsForValue().get(lockKey);            if(0 != nextInt && null != lockValue && nextInt == lockValue){                redisTemplate.delete(lockKey);            }        }    }}

问题六:生效工夫的长短怎么确定,不适合的生效工夫如何解决;

当一个办法中应用redis分布式锁,它的生效工夫确定多少为适合?这个问题能够通过应用redisson框架来解决,示例代码如下;

public class DistributedRedis {    @Autowired    private RedisTemplate<String, Integer> redisTemplate;    @Autowired    private Redisson redisson;    private void buyGoods() {//        synchronized (this){        String lockKey = "stockLock";//        int nextInt = 0;        RLock lock = redisson.getLock(lockKey);        try {            // 默认生效工夫为-1,不生效            lock.lock(15, TimeUnit.SECONDS);//            nextInt = new Random().nextInt(100);            // 针对多实例部署,能够应用setIfAbsent()办法,这个代码理论是redis.setNx()办法            // 给锁设置生效工夫,办法有两种:一种是先设置锁,而后在自行设置生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119);//            redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS);            // 办法二:在设置锁的同时设置其生效工夫//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此办法是对上面的额办法传入的工夫的一个封装//            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, nextInt, 15, TimeUnit.SECONDS);////            if (null == aBoolean || !aBoolean) {//                return;//            }            // 获取key对应的数据            Integer stock = redisTemplate.opsForValue().get("stock");            // 如果库存大于0,对库存减1            if (null != stock && stock > 0) {                int realStock = stock - 1;                // 将批改后的库存放入redis                redisTemplate.opsForValue().set("stock", realStock);                System.out.println("库存扣减胜利,以后库存为:" + realStock);            } else {                System.out.println("库存扣减失败");            }//        }        } finally {            // 操作实现之后,删除锁//            Integer lockValue = redisTemplate.opsForValue().get(lockKey);//            if(0 != nextInt && null != lockValue && nextInt == lockValue){//                redisTemplate.delete(lockKey);//            }            lock.unlock();        }    }}