SpringBoot秒杀系统

秒杀系统库存

今天带来一套秒杀库存扣减

涉及到单体架构和集群架构 希望能给你们带来帮助 我也不想学 但是bgs不教

首先讲一下大致的扣减库存的思路

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
            if (stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                System.out.println("库存扣减成功 剩余库存"+realStock);
                return CommonResult.ok();
            }else{
                System.out.println("库存扣除失败 ,库存不足");
                return CommonResult.fail(888,"库存扣除失败 ,库存不足");
            }
    }
// 这大致就是一个扣减库存的思路 先去判断是否还有库存 然后再去扣减一个库存 替换掉原有的库存数量
// 下面开始说一下利弊
// 利:这样不会出现库存为负数的情况 
// 弊:如果高并发的情况下 多用户可能会造成同时下单同一个编号的商品 (仅次于超卖)

下面说一下这种情况的解决方案

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
        synchronized (this){  // 我们在这里加上一条同步锁 当一个请求执行完毕之后 下一个请求才能进入
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
            if (stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                System.out.println("库存扣减成功 剩余库存"+realStock);
                return CommonResult.ok();
            }else{
                System.out.println("库存扣除失败 ,库存不足");
                return CommonResult.fail(888,"库存扣除失败 ,库存不足");
            }
        }
    }
// 这样虽然可以解决重复下单 解决超卖问题
// 但是也仅限于单体服务器架构 
// 如果是集群架构 就避免不了同时下单的问题

那我们再次优化一下多集群架构重复下单的问题

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
        String lockKey = "lock_key";
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock");  //setnx分布式锁
        if (!result){
            return CommonResult.fail(999,"拥挤中-稍后重试");
        }else{
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
            if (stock>0){
                int realStock = stock -1;
                stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                System.out.println("库存扣减成功 剩余库存"+realStock);
                stringRedisTemplate.delete(lockKey);  //把锁删除
                return CommonResult.ok();
            }else{
                System.out.println("库存扣除失败 ,库存不足");
                stringRedisTemplate.delete(lockKey);  //把锁删除
                return CommonResult.fail(888,"库存扣除失败 ,库存不足");
            }
        }
    }
​
// 虽然多集群不在单机 但是共用一个redis的情况下就不会出现重复下单 使用缓存中间件对其进行一个约束
// 虽然代码到这里已经可以实现了分布式锁 但是还是有问题 下面我们再次优化一下

再次优化代码

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
        String lockKey = "lock_key";
        // 设置一个同步锁过期时间 防止系统挂掉
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock",10,TimeUnit.SECONDS);
        if (!result){
            return CommonResult.fail(999,"拥挤中-稍后重试");
        }else{
            try{   // 防止中级出现异常 
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
                if (stock>0){
                    int realStock = stock -1;
                    stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                    System.out.println("库存扣减成功 剩余库存"+realStock);
                    return CommonResult.ok();
                }else{
                    System.out.println("库存扣除失败 ,库存不足");
                    return CommonResult.fail(888,"库存扣除失败 ,库存不足");
                }
            }finally {  // 不管是否有异常都必须执行
                stringRedisTemplate.delete(lockKey);
            }
        }
    }

到目前为止 代码已经比较完善了 但是还是会有部分问题 在高并发的场景下 如果自己的锁被别人释放的时候 也可能会出现重复下单问题 这个问题也就是出现在finally里面 所以我们最好还是多加一个判断 并且将唯一标识存起来

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
        String lockKey = "lock_key";
        String clientId = String.valueOf(UUID.randomUUID());  // 创建下单人的唯一标识
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId,10,TimeUnit.SECONDS);
        if (!result){
            return CommonResult.fail(999,"拥挤中-稍后重试");
        }else{
            try{
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
                if (stock>0){
                    int realStock = stock -1;
                    stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                    System.out.println("库存扣减成功 剩余库存"+realStock);
                    return CommonResult.ok();
                }else{
                    System.out.println("库存扣除失败 ,库存不足");
                    return CommonResult.fail(888,"库存扣除失败 ,库存不足");
                }
            }finally {
                if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){  // 如果是这个人结束
                    stringRedisTemplate.delete(lockKey);  // 才去释放这个锁 如果不是这个人的时候 不释放这个锁
            }
        }
    }

当到这里的时候 代码量已经比刚开始多了很多了 并且也解决了一个又一个问题 但是在这里还是会有一个问题 当我们代码执行到finally的时候 如果前面的时间已经过了9秒 并且finally中又卡顿了1秒或者2秒 在高并发的情况下 会造成自己的锁已经超时释放了 然后finally执行之后删除了后面那个人的锁 这种问题也是会影响到订单重复的问题

这里就介绍一下Redisson了 如名 这也就是redis的son

依赖部分

        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.8</version>
        </dependency>

启动类部分

   
 @Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
// 其他部分通过@Autowired注入就行了。。

当注入了Redisson之后 我们的代码又可以减少了

@RequestMapping("deduct_stock")
    public CommonResult deductStock(){
        String lockKey = "lock_key";
        // 获取锁对象
            RLock redissonLock = redisson.getLock(lockKey);
            try{
                //加锁
                redissonLock.lock();
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("shop_stock"));
                if (stock>0){
                    int realStock = stock -1;
                    stringRedisTemplate.opsForValue().set("shop_stock",realStock + "");
                    System.out.println("库存扣减成功 剩余库存"+realStock);
                    return CommonResult.ok();
                }else{
                    System.out.println("库存扣除失败 ,库存不足");
                    return CommonResult.fail(888,"库存扣除失败 ,库存不足");
                }
            }finally {
                // 解锁
                redissonLock.unlock();
            }
    }

下图也就是加上redisson之后的流程图

~~~~~~~~~~~~~~~~~~~~~~~ 如果看完对你有帮助 还望一键三连~~~~~~~~~~~~~~~~~~~~~~~ 

猜你喜欢

转载自blog.csdn.net/GSl0408/article/details/128001951
今日推荐