• 电商系统中库存的存储于扣减


    电商系统中,sku的库存是核心单元,本文以Javashop电商系统为例,说明库存的存储于扣减思路

    商品库存更新库存添加和扣减,当用户执行下单操作时 发送消息给MQ, consumer执行扣减库存操作。商家端有单独接口维护库存。

    库存更新主要是操作商品Sku库存信息。SKU是物理上不可分割的最小存货单元。也就是说一款商品,可以根据SKU来确定具体的货物存量。对应es_goods_sku表

    商品库存和商品sku库存

    商品的可用库存和实际库存(actual 实际库存)(enable 可用库存)

    在redis中的键为库存前缀_库存名称_商品id

    {stock}{GOODS_STOCK}_actual_65

    {stock}{GOODS_STOCK}_enable_127

    sku的实际库存和可用库存

    在redis中的键为库存前缀_库存名称_商品skuid

    {stock}{SKU_STOCK}_actual_1320633609858965506

    {stock}{SKU_STOCK}_enable_1310759679300034562

    商家端更新商品库存,通GoodsQuantityManager.updateSkuQuantity() 将sku和商品信息更新到redis和数据库 如果lua脚本执行成功,判断javashopConfig配置中是否开启缓冲池,如果开启则更新缓冲区库存,未开启则同步数据到数据库库存。

    首先查出缓存商品信息 包括商品信息 和商品sku信息

    @PutMapping
    public void updateQuantity(@ApiIgnore@Valid @RequestBody List<GoodsSkuQuantityVO>  skuQuantityList, @PathVariable("goods_id")Long goodsId)  {
    
       CacheGoods goods = goodsQueryManager.getFromCache(goodsId);
    
       Seller seller = UserContext.getSeller();
       if(goods == null || !goods.getSellerId().equals(seller.getSellerId())){
          throw new ServiceException(GoodsErrorCode.E307.code(), "没有操作权限");
       }
    
       // 原有的sku集合
       List<GoodsSkuVO> skuList = goods.getSkuList();
       Map<Long,GoodsSkuVO> skuMap = new HashMap<>(skuList.size());
       for(GoodsSkuVO sku : skuList){
          skuMap.put(sku.getSkuId(), sku);
       }

    校验库存 判断商品库存数量,是否具有sku信息和待发货数量 代发货数量必须小于可用库存数量,实际库存是设置后固定,可用库存是当前剩余库存量

    //要更新的库存列表
    List<GoodsQuantityVO> stockList = new ArrayList<>();
    
    for (GoodsSkuQuantityVO quantity : skuQuantityList) {
    
       if (quantity.getQuantityCount() == null || quantity.getQuantityCount() < 0 ) {
          throw new ServiceException(GoodsErrorCode.E307.code(), "sku总库存不能为空或负数");
       }
    
       GoodsSkuVO sku = skuMap.get(quantity.getSkuId());
       if(sku == null){
          throw new ServiceException(GoodsErrorCode.E307.code(), "商品sku不存在");
       }
       //待发货数
       Integer waitRogCount = sku.getQuantity()-sku.getEnableQuantity();
       //判断库存是否小于待发货数
       if (quantity.getQuantityCount()<waitRogCount) {
          throw new ServiceException(GoodsErrorCode.E307.code(), "sku库存数不能小于待发货数");
       }

    实际库存和可用库存 库存

    //实际库存
       GoodsQuantityVO actualQuantityVo = new GoodsQuantityVO();
       //用传递的数量-现有的,就是变化的,如传递的是2000,原来是200,则就+1800,如果传递的是100,原来是200则就是-100
       int stockNum = quantity.getQuantityCount() -sku.getQuantity();
       actualQuantityVo.setQuantity(stockNum );
       actualQuantityVo.setGoodsId(goodsId);
       actualQuantityVo.setQuantityType(QuantityType.actual);
       actualQuantityVo.setSkuId(quantity.getSkuId());
    
       stockList.add(actualQuantityVo);
    
       //clone 一个quantity vo 设置为更新可用库存
       try {
          GoodsQuantityVO enableVo =(GoodsQuantityVO)    actualQuantityVo.clone();
          enableVo.setQuantityType(QuantityType.enable);
          stockList.add(enableVo);
       } catch (CloneNotSupportedException e) {
          throw new ServiceException(GoodsErrorCode.E307.code(), "goodsQuantityVo clone error");
       }
    
    }

    更新库存 数据库和缓存中都需要更新,当开启缓冲池并且缓冲池中数据已经饱和 则同步更新数据库,如果未开启缓冲池,则实时同步商品数据库中的库存数据

    //更新库存
    this.goodsQuantityManager.updateSkuQuantity(stockList);
    
    //如果商品库存缓冲池开启了,那么需要立即同步数据库的商品库存,以保证商品库存显示正常
    if (javashopConfig.isStock()) {
       //立即同步数据库的库存
       goodsQuantityManager.syncDataBase();
    }

    更新sku库存采用redis+lua脚本 利用redis原子性避免超卖问题

    public Boolean updateSkuQuantity(List<GoodsQuantityVO> goodsQuantityList) {
    
        List<Long> skuIdList = new ArrayList();
        List<Long> goodsIdList = new ArrayList();
    
        List keys = new ArrayList<>();
        List values = new ArrayList<>();
    
        for (GoodsQuantityVO quantity : goodsQuantityList) {
    
            Assert.notNull(quantity.getGoodsId(), "goods id must not be null");
            Assert.notNull(quantity.getSkuId(), "sku id must not be null");
            Assert.notNull(quantity.getQuantity(), "quantity id must not be null");
            Assert.notNull(quantity.getQuantityType(), "Type must not be null");
    
    
            //sku库存
            if (QuantityType.enable.equals(quantity.getQuantityType())) {
                keys.add(StockCacheKeyUtil.skuEnableKey(quantity.getSkuId()));
            } else if (QuantityType.actual.equals(quantity.getQuantityType())) {
                keys.add(StockCacheKeyUtil.skuActualKey(quantity.getSkuId()));
            }
            values.add("" + quantity.getQuantity());
    
            //goods库存key
            if (QuantityType.enable.equals(quantity.getQuantityType())) {
                keys.add(StockCacheKeyUtil.goodsEnableKey(quantity.getGoodsId()));
            } else if (QuantityType.actual.equals(quantity.getQuantityType())) {
                keys.add(StockCacheKeyUtil.goodsActualKey(quantity.getGoodsId()));
            }
            values.add("" + quantity.getQuantity());
    
    
            skuIdList.add(quantity.getSkuId());
            goodsIdList.add(quantity.getGoodsId());
        }
    
        RedisScript<Boolean> redisScript = getRedisScript();
        Boolean result = stringRedisTemplate.execute(redisScript, keys, values.toArray());
    
        logger.debug("更新库存:");
        logger.debug(goodsQuantityList.toString());
        logger.debug("更新结果:" + result);
    
        //如果lua脚本执行成功则记录缓冲区
        if (result) {
    
            //判断配置文件中设置的商品库存缓冲池是否开启
            if (javashopConfig.isStock()) {
    
                //是否需要同步数据库
                boolean needSync = getSkuPool().oneTime(skuIdList);
                getGoodsPool().oneTime(goodsIdList);
    
                logger.debug("是否需要同步数据库:" + needSync);
                logger.debug(getSkuPool().toString());
    
                //如果开启了缓冲池,并且缓冲区已经饱和,则同步数据库
                if (needSync) {
                    syncDataBase();
                }
            } else {
                //如果未开启缓冲池,则实时同步商品数据库中的库存数据
                syncDataBase(skuIdList, goodsIdList);
            }
    
        }
    
    
        return result;
    }
  • 相关阅读:
    python的select和epoll
    ibatis annotations 注解方式返回刚插入的自增长主键ID的值
    java web 项目中获取当前路径的几种方法
    Servlet的监听器
    mybatis-配置文件mybatis-config.xml
    数据库死锁
    JDBC控制事务
    server.xml 解析
    linux下Tomcat 安装后执行startup.sh,出现– Cannot find …bin/catalina.sh
    jni 类初始化失败(nested exception is java.lang.NoClassDefFoundError)
  • 原文地址:https://www.cnblogs.com/javashop-docs/p/13954106.html
Copyright © 2020-2023  润新知