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

前言

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

正文

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

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

- nginx.conf配置文件修改

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

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

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

- jmeter测试结果

- 数据库库存扣减结果

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

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

- 修改WmsStockServiceImpl类中的checkAndReduceStock方法

- 通过sql扣减库存

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ht.atp.plat.mapper.WmsStockMapper">

    <update id="checkAndReduceStock">
        update wms_stock
        set stock_quantity = (stock_quantity - #{reduceStock})
        where id = 1 and (stock_quantity - #{reduceStock}) >= 0;
    </update>
</mapper>

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

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

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

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

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

- 修改WmsStockServiceImpl类中的checkAndReduceStock方法

- 使用for update加锁查询库存

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

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

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

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

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

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

- 修改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<WmsStock>().eq("id", wmsStock.getId()).eq("version", version));
            if (update == 0) {
                checkAndReduceStock();
            }
        }
    }

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

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

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

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

  • 三种锁测试结果总结

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

mysql锁测试结果
锁类型平均访问时间吞吐量
行锁214ms455
悲观锁528ms185
乐观锁1447ms65

结语

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

厉害哥哥吖

您的支持是我创作下去的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值