SpringBoot flash kill system

Flash sale system inventory

Today we bring a set of flash sale inventory discounts

It involves single architecture and cluster architecture. I hope it can be helpful to you. I don’t want to learn it, but bgs doesn’t teach it.

First, let’s talk about the general idea of ​​deducting inventory.

@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,"库存扣除失败 ,库存不足");
            }
    }
// 这大致就是一个扣减库存的思路 先去判断是否还有库存 然后再去扣减一个库存 替换掉原有的库存数量
// 下面开始说一下利弊
// 利:这样不会出现库存为负数的情况 
// 弊:如果高并发的情况下 多用户可能会造成同时下单同一个编号的商品 (仅次于超卖)

Let’s talk about the solution to this situation

@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,"库存扣除失败 ,库存不足");
            }
        }
    }
// 这样虽然可以解决重复下单 解决超卖问题
// 但是也仅限于单体服务器架构 
// 如果是集群架构 就避免不了同时下单的问题

Then let’s optimize the problem of repeated orders in the multi-cluster architecture again.

@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的情况下就不会出现重复下单 使用缓存中间件对其进行一个约束
// 虽然代码到这里已经可以实现了分布式锁 但是还是有问题 下面我们再次优化一下

Optimize code again

@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);
            }
        }
    }

So far, the code has been relatively complete, but there are still some problems. In high-concurrency scenarios, if your lock is released by others, duplicate orders may also occur. This problem also appears in finally, so we'd better do it. Add one more judgment and save the unique identifier

@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);  // 才去释放这个锁 如果不是这个人的时候 不释放这个锁
            }
        }
    }

When we get here, the amount of code is much larger than at the beginning, and one problem after another has been solved. But there will still be a problem here. When our code is executed to finally, if the previous time has passed 9 seconds and finally If it is stuck for 1 second or 2 seconds, in the case of high concurrency, your own lock will be released over time, and then the lock of the person behind you will be deleted after finally executing. This problem will also affect the issue of order duplication.

Here is an introduction to Redisson. As the name suggests, it is also the son of redis.

Dependency part

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

Startup class section

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

After injecting Redisson, our code can be reduced

@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();
            }
    }

The figure below is the flow chart after adding redisson

~~~~~~~~~~~~~~~~~~~~~~~ If reading this helps you, please click three times~~~~~~~~~~~~~~ ~~~~~~~~~ 

Guess you like

Origin blog.csdn.net/GSl0408/article/details/128001951