在上一节内容中,我们详细介绍了超卖问题产生的原因,以及在单应用的项目中,如何解决超卖的问题——通过jvm本地锁控制并发访问从而解决“超卖问题”。同时我们也提出本地锁只能解决单应用服务的超卖问题,本节内容我们话接上篇,使用传统锁的方式解决在多应用服务访问中的并发问题。主要是通过mysql的乐观锁和悲观锁解决解决并发问题。
- nginx.conf配置文件修改
- 访问接口,可以正常访问扣减库存接口,nginx代理已生效
- jmeter测试结果
- 数据库库存扣减结果
ps: 由测试结果可以得出,扣减库存为4810,应用在使用jvm本地锁的情况下,并不能解决并发“超卖的问题”,依然出现了并发访问的问题。
- 修改WmsStockServiceImpl类中的checkAndReduceStock方法
- 通过sql扣减库存
"1.0" encoding="UTF-8"?> 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>
- jmeter测试结果:平均访问时间214ms,吞吐量为每秒455
- 数据库扣减库存结果:0
PS:通过mysql的行锁可以解决并发访问出现的“超卖”问题
- 修改WmsStockServiceImpl类中的checkAndReduceStock方法
- 使用for update加锁查询库存
- jmeter测试结果:平均访问时间528ms,吞吐量为每秒185
- 数据库库存扣减结果:0
PS:通过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(); } } }
- jmeter测试结果:平均访问时间1447ms,吞吐量为每秒65
- 数据库库存扣减结果:0
PS:通过mysql的乐观锁可以解决并发访问出现的“超卖”问题,这里我们需要加入重试机制,否则会出现大量请求执行结果失败的问题。
从测试结果来看:如果追求极致性能、业务场景简单并且不需要记录数据前后变化的情况下优先选择行锁;如果写并发量较低(多读),争抢不是很激烈的情况下优先选择乐观锁,如果写并发量较高,一般会经常冲突,此时选择乐观锁的话,会导致业务代码不间断的重试;系统中一般选择悲观锁。
锁类型 | 平均访问时间 | 吞吐量 |
---|---|---|
行锁 | 214ms | 455 |
悲观锁 | 528ms | 185 |
乐观锁 | 1447ms | 65 |
关于使用传统锁解决“超卖”问题的内容到这里就结束了,我们下期见。。。。。。