电商系统中库存的存储于扣减
電商系統(tǒng)中,sku的庫存是核心單元,本文以Javashop電商系統(tǒng)為例,說明庫存的存儲(chǔ)于扣減思路
商品庫存更新庫存添加和扣減,當(dāng)用戶執(zhí)行下單操作時(shí) 發(fā)送消息給MQ, consumer執(zhí)行扣減庫存操作。商家端有單獨(dú)接口維護(hù)庫存。
庫存更新主要是操作商品Sku庫存信息。SKU是物理上不可分割的最小存貨單元。也就是說一款商品,可以根據(jù)SKU來確定具體的貨物存量。對應(yīng)es_goods_sku表
商品庫存和商品sku庫存
商品的可用庫存和實(shí)際庫存(actual 實(shí)際庫存)(enable 可用庫存)
在redis中的鍵為庫存前綴_庫存名稱_商品id
{stock}{GOODS_STOCK}_actual_65
{stock}{GOODS_STOCK}_enable_127
sku的實(shí)際庫存和可用庫存
在redis中的鍵為庫存前綴_庫存名稱_商品skuid
{stock}{SKU_STOCK}_actual_1320633609858965506
{stock}{SKU_STOCK}_enable_1310759679300034562
商家端更新商品庫存,通GoodsQuantityManager.updateSkuQuantity() 將sku和商品信息更新到redis和數(shù)據(jù)庫 如果lua腳本執(zhí)行成功,判斷javashopConfig配置中是否開啟緩沖池,如果開啟則更新緩沖區(qū)庫存,未開啟則同步數(shù)據(jù)到數(shù)據(jù)庫庫存。
首先查出緩存商品信息 包括商品信息 和商品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(), "沒有操作權(quán)限");
}
// 原有的sku集合
List<GoodsSkuVO> skuList = goods.getSkuList();
Map<Long,GoodsSkuVO> skuMap = new HashMap<>(skuList.size());
for(GoodsSkuVO sku : skuList){
skuMap.put(sku.getSkuId(), sku);
}
校驗(yàn)庫存 判斷商品庫存數(shù)量,是否具有sku信息和待發(fā)貨數(shù)量 代發(fā)貨數(shù)量必須小于可用庫存數(shù)量,實(shí)際庫存是設(shè)置后固定,可用庫存是當(dāng)前剩余庫存量
//要更新的庫存列表
List<GoodsQuantityVO> stockList = new ArrayList<>();
for (GoodsSkuQuantityVO quantity : skuQuantityList) {
if (quantity.getQuantityCount() == null || quantity.getQuantityCount() < 0 ) {
throw new ServiceException(GoodsErrorCode.E307.code(), "sku總庫存不能為空或負(fù)數(shù)");
}
GoodsSkuVO sku = skuMap.get(quantity.getSkuId());
if(sku == null){
throw new ServiceException(GoodsErrorCode.E307.code(), "商品sku不存在");
}
//待發(fā)貨數(shù)
Integer waitRogCount = sku.getQuantity()-sku.getEnableQuantity();
//判斷庫存是否小于待發(fā)貨數(shù)
if (quantity.getQuantityCount()<waitRogCount) {
throw new ServiceException(GoodsErrorCode.E307.code(), "sku庫存數(shù)不能小于待發(fā)貨數(shù)");
}
實(shí)際庫存和可用庫存 庫存
//實(shí)際庫存
GoodsQuantityVO actualQuantityVo = new GoodsQuantityVO();
//用傳遞的數(shù)量-現(xiàn)有的,就是變化的,如傳遞的是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 一個(gè)quantity vo 設(shè)置為更新可用庫存
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");
}
}
更新庫存 數(shù)據(jù)庫和緩存中都需要更新,當(dāng)開啟緩沖池并且緩沖池中數(shù)據(jù)已經(jīng)飽和 則同步更新數(shù)據(jù)庫,如果未開啟緩沖池,則實(shí)時(shí)同步商品數(shù)據(jù)庫中的庫存數(shù)據(jù)
//更新庫存
this.goodsQuantityManager.updateSkuQuantity(stockList);
//如果商品庫存緩沖池開啟了,那么需要立即同步數(shù)據(jù)庫的商品庫存,以保證商品庫存顯示正常
if (javashopConfig.isStock()) {
//立即同步數(shù)據(jù)庫的庫存
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("更新結(jié)果:" + result);
//如果lua腳本執(zhí)行成功則記錄緩沖區(qū)
if (result) {
//判斷配置文件中設(shè)置的商品庫存緩沖池是否開啟
if (javashopConfig.isStock()) {
//是否需要同步數(shù)據(jù)庫
boolean needSync = getSkuPool().oneTime(skuIdList);
getGoodsPool().oneTime(goodsIdList);
logger.debug("是否需要同步數(shù)據(jù)庫:" + needSync);
logger.debug(getSkuPool().toString());
//如果開啟了緩沖池,并且緩沖區(qū)已經(jīng)飽和,則同步數(shù)據(jù)庫
if (needSync) {
syncDataBase();
}
} else {
//如果未開啟緩沖池,則實(shí)時(shí)同步商品數(shù)據(jù)庫中的庫存數(shù)據(jù)
syncDataBase(skuIdList, goodsIdList);
}
}
return result;
}
總結(jié)
以上是生活随笔為你收集整理的电商系统中库存的存储于扣减的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何查看服务器对外的IP
- 下一篇: Es6主要特征详解