(二)库存超卖案例实战——使用传统锁解决“超卖”问题

前言

在上一节内容中,我们详细介绍了超卖问题产生的原因,以及在单应用的项目中,如何解决超卖的问题——通过jvm本地锁控制并发访问从而解决“超卖问题”。同时我们也提出本地锁只能解决单应用服务的超卖问题,本节内容我们话接上篇,使用传统锁的方式解决在多应用服务访问中的并发问题。主要是通过mysql的乐观锁和悲观锁解决解决并发问题。

正文

  • 开启idea的allow parallel run功能,开启三个相同服务的应用,端口分别为7000,7001,7002

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第1张图片

  • 使用nginx代理以上三个服务,实现并发访问

- nginx.conf配置文件修改

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第2张图片

- 访问接口,可以正常访问扣减库存接口,nginx代理已生效

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第3张图片

  • 将库存数量修改为10000,使用jmeter测试并发访问接口

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第4张图片

  • 使用jmeter并发访问测试,结果说明

- jmeter测试结果

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第5张图片

- 数据库库存扣减结果

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第6张图片

ps: 由测试结果可以得出,扣减库存为4810,应用在使用jvm本地锁的情况下,并不能解决并发“超卖的问题”,依然出现了并发访问的问题。

  • 通过使用mysql的行锁,使用一个sql解决并发访问问题

- 修改WmsStockServiceImpl类中的checkAndReduceStock方法

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第7张图片

- 通过sql扣减库存





    
        update wms_stock
        set stock_quantity = (stock_quantity - #{reduceStock})
        where id = 1 and (stock_quantity - #{reduceStock}) >= 0;
    

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第8张图片

  •  将库存修改为10000,重启服务,使用jmeter压测修改后的扣减库存接口

- jmeter测试结果:平均访问时间214ms,吞吐量为每秒455

- 数据库扣减库存结果:0

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第9张图片

PS:通过mysql的行锁可以解决并发访问出现的“超卖”问题

  •  使用mysql的悲观锁解决并发访问的“超卖”问题

- 修改WmsStockServiceImpl类中的checkAndReduceStock方法

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第10张图片

- 使用for update加锁查询库存

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第11张图片

  •  再次将库存修改为10000,重启修改后的服务,使用jmeter压测修改后的扣减库存接口 

- jmeter测试结果:平均访问时间528ms,吞吐量为每秒185

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第12张图片

- 数据库库存扣减结果:0

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第13张图片

PS:通过mysql的悲观锁可以解决并发访问出现的“超卖”问题,平均访问时间有所增加,吞吐量也有所下降,相对于行锁。需要注意的是,该查询库存和更新库存的操作必须放在同一个本地事务中,否则悲观锁将失效。悲观锁只有在本次操作全部完成事务提交之后才会释放锁。如果不在同一个事务中,锁提前释放去更新库存还是会存在并发的问题。

  •   使用mysql的乐观锁解决并发访问的“超卖”问题

- 在数据库wms_stock表中新增一个字段version,通过version版本号字段控制并发访问

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第14张图片

- 修改WmsStockServiceImpl类中的checkAndReduceStock方法

    @Override
    public void checkAndReduceStock() {
        // 查询库存
        WmsStock wmsStock = baseMapper.selectWmsStockForUpdate(1L);
        // 验证库存大于0再扣减库存
        if (wmsStock != null && wmsStock.getStockQuantity() > 0) {
            // 获取版本号
            Integer version = wmsStock.getVersion();
            //更新版本号
            wmsStock.setVersion(version+1);
            wmsStock.setStockQuantity(wmsStock.getStockQuantity() - 1);
            
            // 更新之前先判断是否是之前查询的那个版本,如果不是重试
            int update = baseMapper.update(wmsStock, new UpdateWrapper().eq("id", wmsStock.getId()).eq("version", version));
            if (update == 0) {
                checkAndReduceStock();
            }
        }
    }

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第15张图片

  • 再次将库存修改为10000,重启修改后的服务,使用jmeter压测修改后的扣减库存接口  

- jmeter测试结果:平均访问时间1447ms,吞吐量为每秒65

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第16张图片

- 数据库库存扣减结果:0

(二)库存超卖案例实战——使用传统锁解决“超卖”问题_第17张图片

PS:通过mysql的乐观锁可以解决并发访问出现的“超卖”问题,这里我们需要加入重试机制,否则会出现大量请求执行结果失败的问题。

  • 三种锁测试结果总结

从测试结果来看:如果追求极致性能、业务场景简单并且不需要记录数据前后变化的情况下优先选择行锁;如果写并发量较低(多读),争抢不是很激烈的情况下优先选择乐观锁,如果写并发量较高,一般会经常冲突,此时选择乐观锁的话,会导致业务代码不间断的重试;系统中一般选择悲观锁。

mysql锁测试结果
锁类型 平均访问时间 吞吐量
行锁 214ms 455
悲观锁 528ms 185
乐观锁 1447ms 65

结语

关于使用传统锁解决“超卖”问题的内容到这里就结束了,我们下期见。。。。。。

你可能感兴趣的:(ATP应用测试平台,#,springboot,#,分布式锁,spring,boot)