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


    前言

    在上一节内容中,我们详细介绍了超卖问题产生的原因,以及在单应用的项目中,如何解决超卖的问题——通过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扣减库存

    1. "1.0" encoding="UTF-8"?>
    2. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.ht.atp.plat.mapper.WmsStockMapper">
    4. <update id="checkAndReduceStock">
    5. update wms_stock
    6. set stock_quantity = (stock_quantity - #{reduceStock})
    7. where id = 1 and (stock_quantity - #{reduceStock}) >= 0;
    8. update>
    9. 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方法

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

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

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

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

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

    • 三种锁测试结果总结

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

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

    结语

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

  • 相关阅读:
    excel vba 制作 kml 文档时,如何写入有引号的字符串
    BUUCTF 刮开有奖 1
    openstack 遇到的error
    java计算机毕业设计计算机网络精品课程网站源码+mysql数据库+系统+lw文档+部署
    遍历Opencv中Mat中每个像素的值
    概率论基础__排列与组合
    某金融机构分布式数据库架构方案与运维方案设计分享
    【UOJ 494】DNA序列(贪心)(Lyndon分解)
    解决“您点击的链接已过期”;The Link You Followed Has Expired的问题
    操作系统中的(进程,线程)
  • 原文地址:https://blog.csdn.net/yprufeng/article/details/133982390