美文网首页JAVA
高并发下库存问题,新增version字段来解决超卖

高并发下库存问题,新增version字段来解决超卖

作者: flyjar | 来源:发表于2022-07-20 17:33 被阅读0次

在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,Redis原子操作等。本篇通过MySQL乐观锁来演示基本实现。

一、Goods和Order
@Data
public class Goods {
    private int id;
    private String name;
    private int stock;
    private int version;
}
@Data
public class Order {
    private int id;
    private int uid;
    private int gid;
}
二、OrderDao和GoodsDao
@Mapper
public interface OrderDao {

    /**
     * 插入订单
     * 注意: 由于order是sql中的关键字,所以表名需要加上反引号
     * @param order
     * @return int
     */
    @Insert("INSERT INTO `order` (uid, gid) VALUES (#{uid}, #{gid})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertOrder(Order order);
}
@Mapper
public interface GoodsDao {

    /**
     * 查询商品库存
     * @param id 商品id
     * @return
     */
    @Select("SELECT * FROM goods WHERE id = #{id}")
    Goods getStock(@Param("id") int id);

    /**
     * 乐观锁方案扣减库存
     * @param id 商品id
     * @param version 版本号
     * @return
     */
    @Update("UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version}")
    int decreaseStockForVersion(@Param("id") int id, @Param("version") int version);
}
三、GoodsService(重点)
@Service
@Slf4j
public class GoodsService {

    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private OrderDao orderDao;

    /**
     * 扣减库存
     * @param gid 商品id
     * @param uid 用户id
     * @return SUCCESS 1 FAILURE 0
     */
    public int sellGoods(int gid, int uid) {
        int retryCount = 0;
        int update = 0;
        // 获取库存
        Goods goods = goodsDao.getStock(gid);
        if (goods.getStock() > 0) {
            // 乐观锁更新库存
            // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
            // 最多重试3次


//为什么要重试呢?
//因为 Goods goods = goodsDao.getStock(gid);的时候同时执行的,查询出来的version都是1,库存都是10。但是去执行reduceStock() 的时候,只会有一个执行成功。但是的这个时候实际上还有库存,只是version变化了,导致扣减失败,所以这里尝试了多次进行扣减,直至成功或超出三次

            while(retryCount < 3 && update == 0){
                update = this.reduceStock(gid);
                retryCount++;
            }
            if(update == 0){
                log.error("库存不足");
                return 0;
            }
            // 库存扣减成功,生成订单
            Order order = new Order();
            order.setUid(uid);
            order.setGid(gid);
            int result = orderDao.insertOrder(order);
            return result;
        }
        // 失败返回
        return 0;
    }


    /**
     * 减库存
     *
     * 由于默认的事务隔离级别是可重复读,会导致在同一个事务中查询3次goodsDao.getStock()
     * 得到的数据始终是相同的,所以需要单独提取reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
     */
    @Transactional(rollbackFor = Exception.class)
    public  int  reduceStock(int gid){
        int result = 0;
        //1、查询商品库存
        Goods goods = goodsDao.getStock(gid);
        //2、判断库存是否充足
        if(goods.getStock() >0){
            //3、减库存
            // 乐观锁更新库存
            result = goodsDao.decreaseStockForVersion(gid, goods.getVersion());
        }
        return result;
    }
}
四、单元测试GoodsServiceTest
@SpringBootTest
class GoodsServiceTest {

    @Autowired
    GoodsService goodsService;

    @Test
    void seckill() throws InterruptedException {

        // 库存初始化为10,这里通过CountDownLatch和线程池模拟100个并发
        int threadTotal = 100;

        ExecutorService executorService = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadTotal);
        for (int i = 0; i < threadTotal ; i++) {
            int uid = i;
            executorService.execute(() -> {
                try {
                    goodsService.sellGoods(1, uid);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();

    }
}
五、最终结果

库存由10减到了0,并且生产了10条订单记录。

相关文章

  • 高并发下库存问题,新增version字段来解决超卖

    在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方...

  • 关于秒杀抢库存相关的一点理解

    秒杀场景通常需要解决两个问题:1、高并发2、库存超卖 对于高并发问题,我们可以使用缓存系统来解决,避免直接对数据库...

  • 电商技术 -- 库存设计指北

    前言 最近在解决一套老电商系统的库存"超卖"问题。一直以为超卖问题,最难解决的是库存扣减,实则不然,我们的系统在解...

  • Redis解决秒杀

    1.不加锁和事务问题:产生超卖 2.解决超卖问题,加上事务和锁问题:产生 库存剩余,连接超时 3.解决问题:产生...

  • B2B2C商城和ERP对接中的超卖问题

    1.什么是超卖问题? 商城平台中超卖问题是经常发生的,那什么是超卖问题?超卖问题就是已售商品库存 > 实际库存,现...

  • 通过乐观锁解决库存超卖的问题

    前言 在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术...

  • 超卖

    mysql处理高并发,防止库存超卖

  • 并发下的库存如何扣?

    并发下的库存如何扣? 背景 业务反馈,项目出现库存超卖/负值现象。 原因 假设库存为5,用户一次买了1个,于是库存...

  • 高并发下的抢购/秒杀功能

    抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: ...

  • redis实现高并发下的抢购/秒杀功能

    抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: ...

网友评论

    本文标题:高并发下库存问题,新增version字段来解决超卖

    本文链接:https://www.haomeiwen.com/subject/aiqyirtx.html