[分布式锁] Springboot整合Redisson实现超卖问题还原和分析

超卖简单代码

写一段简单正常的超卖逻辑代码,多个用户同时操作同一段数据,探究出现的问题。

Redis有库存数量为100;如果大于0,则扣减1,重新存储Redis中;
运行代码测试
@RestController
@RequestMapping("/redisson")
@Api(tags = "Redisson模块")
@Slf4j
public class RedissonController {

    @Resource
    RedissonClient redissonClient;
    @Resource
    RedisService redisService;

    /**
     * 设置库存
     * @param stock
     * @return
     */
    @GetMapping("/setStock")
    @ApiOperation(value = "setStock")
    @ApiOperationSupport(order = 0)
    public String setStock(Integer stock){
        redisService.setCacheObject("stock", stock);
        return "ok";
    }

    @GetMapping("/reduceStock")
    @ApiOperation(value = "reduceStock")
    @ApiOperationSupport(order = 1)
    public String reduceStock(){
        // 获取Redis数据库中的商品数量
        int stock = redisService.getCacheObject("stock");
        // 减库存
        if(stock > 0) {
            stock = stock-1;
            redisService.setCacheObject("stock", stock);
            log.info("商品扣减成功,剩余商品:"+stock);
        }else {
            log.info("库存不足.....");
        }
        return "ok";
    }
}


单体应用的并发

在单应用模式下,使用Apifox压测,并发线程数20个

 测试结果:

每个请求相当于一个线程,当几个线程同时拿到数据时,线程A拿到库存为84,这个时候线程B也进入程序,并且抢占了CPU,访问库存为84,最后两个线程都对库存减一,导致最后修改为83,实际上多卖出去了一件

这里多个线程处理一个变量导致了并发问题,我们使用synchronized进行处理

使用synchronized同步锁

依旧还是先测试单服务器

    @GetMapping("/reduceStock2")
    @ApiOperation(value = "reduceStock2")
    @ApiOperationSupport(order = 2)
    public String reduceStock2(){
        synchronized (this) {
            // 获取Redis数据库中的商品数量
            int stock = redisService.getCacheObject("stock");
            // 减库存
            if(stock > 0) {
                stock = stock-1;
                redisService.setCacheObject("stock", stock);
                log.info("商品扣减成功,剩余商品:"+stock);
            }else {
                log.info("库存不足.....");
            }
        }
        return "ok";
    }

库存100

重新压测,得到的日志信息如下所示: 

 可以看到,按顺序扣减成功


分布式应用的并发

 在单机模式下,添加synchronized关键字,的确能够避免商品的超卖现象!

但是在分布式微服务中,针对该服务设置了集群,synchronized依旧还能保证数据的正确性吗?

假设多个请求,被注册中心负载均衡,每个微服务中的该处理接口,都添加有synchronized,

 依然会出现类似的超卖问题:

synchronized只是针对单一服务器JVM进行加锁,但是分布式是很多个不同的服务器,导致两个线程或多个在不同服务器上共同对商品数量信息做了操作!

我们复制刚才的应用并设置两个端口8000、8001。然后开两个Apifox并设置并发数20,分别点击进行模拟分布式并发

测试结果:

可以看到40次请求,正确扣减后的库存应为60,但最后库存为77,少扣了17个

使用Redission分布式锁

我们引入Redission分布式锁,并改造一下代码

@GetMapping("/reduceStock3")
    @ApiOperation(value = "reduceStock3")
    @ApiOperationSupport(order = 3)
    public String reduceStock3(){
        RLock rLock = redissonClient.getLock("lock:stock");

        //尝试获取分布式锁20秒,获取失败则返回
        boolean isSuccess = true;
        try {
            isSuccess = rLock.tryLock(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            isSuccess = false;
        }
        if (!isSuccess){
            return "fail";
        }
        try{
            // 获取Redis数据库中的商品数量
            int stock = redisService.getCacheObject("stock");
            // 减库存
            if(stock > 0) {
                stock = stock -1;
                redisService.setCacheObject("stock", stock);
                System.out.println("商品扣减成功,剩余商品:"+stock);
            }else {
                System.out.println("库存不足.....");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rLock.unlock();
        }
        return "ok";
    }

所有应用在访问扣减库存接口的时候,必须先获取Redission分布式锁, 尝试获取时间设置为20秒,如果20秒之内没获取,则直接返回获取失败,如果线程成功获取锁,则执行业务逻辑,锁的超时时间默认30s,看门狗默认10s会进行续期,将超时时间重置为30s,当业务执行完,则释放锁

开始分布式压力测试:

 可以看到40个请求都成功。我们看看数据是否正常

 可以看到,库存成功扣减。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值