電商系統中庫存的存儲於扣減


電商系統中,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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM