接下来就是启动下
调试下,没有问题,我们去压测下,因为正常情况下我们需要压测我们的接口,我们用下jMeter,
我们去并发请求,
肯定有成功,有失败,我们去看下,我们的数据库,。
我们发现,我们的商品发超了。可能是在扣库存的其他的线程也在操作,没有做区分,就导致了超发,这样我们可以用乐观锁 悲观锁,或者reids来实现。
我们实现下悲观锁,
<?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.example.product.demo.dao.ProductDao"> <select id="getProduct" parameterType="long" resultType="ProductPo"> SELECT id,`name`,stock,price,version,note FROM product WHERE id=#{id} for update </select> <update id="decreaseProduct"> update product set stock=stock-#{quantity} where id=#{id} </update> </mapper>
这样就实现了悲观锁,我们来测试下,
我们看下数据库,
没有出现超发现象,但是出现了性能有所下降的问题,可以去查看购买记录,但是这样能保证我们的发售的商品不超卖,牺牲一些性能的,
我们看下乐观锁,用乐观锁来实现下,我们使用版本号字段来控制,版本号增加,扣库存,
<update id="decreaseProduct"> update product set stock=stock-#{quantity},version=version+1 where id=#{id} and version=#{version} </update>
对应mapper修改
@Mapper public interface ProductDao { public ProductPo getProduct(Long id); public int decreaseProduct(@Param("id") Long id, @Param("quantity") int quantity, @Param("version") int version); }
修改逻辑代码
@Override @Transactional public boolean purchase(Long userId, Long productid, int quantity) { ProductPo productPo=productDao.getProduct(productid); if (productPo.getStock()<quantity){ return false; } int version=productPo.getVersion(); int reslut=productDao.decreaseProduct(productid,quantity,version); if (reslut==0){ return false; } PurchaseRecordPo purchaseRecordPo=initpush(userId,productPo,quantity); puchaesre.insertPurcha(purchaseRecordPo); return true; }
完成后,我们去修改下调试下,然后进行并发压测,
我们发现了,错误率上升了,看下记录,发现部分记录没有增加进去。但是库存扣减了,我们这个时候可以利用增加重入次数,来对错误的进行重试。
@Override @Transactional public boolean purchase(Long userId, Long productid, int quantity) { for(int i=0;i<3;i++){ ProductPo productPo=productDao.getProduct(productid); if (productPo.getStock()<quantity){ //库存不足 return false; } //获取版本号 int version=productPo.getVersion(); int reslut=productDao.decreaseProduct(productid,quantity,version); //扣库存失败 if (reslut==0){ //重试 continue; } PurchaseRecordPo purchaseRecordPo=initpush(userId,productPo,quantity); puchaesre.insertPurcha(purchaseRecordPo); return true; } return false; }
这样增加重试机制后,错误次数减少。 今个是可以发现,其实上这样操作是保证了扣减库存的增强,但是一般在企业中 通常考虑用NoSQl作为解决方案,比较常用的是redis,大概的思路是利用redis响应高并发的用户请求,定时任务将redis的购买信息保存到数据库中。