一. 问题背景
背景起因在单例模式之懒加载模式。对懒加载不熟悉的伙伴推荐先阅读单例模式之懒加载模式。此篇只对电商库存做分析。
二. 前言
电商的库存是用redis做缓存来解决的,估计是应对秒杀活动时页面流量很高,频繁读取数据库效果不佳,也会出现超卖现象。
三. 创建订单
获取库存的缘于创建订单,如下是创建订单涉及到的过程:
四. 加载sku信息并锁库存
五. 获取库存的代码
先简单浏览下面2个代码块,无需纠结某处。看的过程中,思考是否与单例模式懒加载的双重检查锁定的写法有相同
/**
* 通过productId获取商品库存信息
* @param productId 商品ID
* @param shopId 店铺ID
* @return
*/
@Override
public List<ProductVariantInventoryDTO> getProductInventoryByProductId(Long shopId, Long productId){
//从缓存中取
List<ProductVariantInventoryDTO> productVariantInventoryDTOS = productInventoryCache.getProductVariantInventoryDTOFromCache(shopId ,productId);
// 第一次判空
if(productVariantInventoryDTOS == null){
productVariantInventoryDTOS = getInventoryByVariantIdFromDB(shopId, productId);
}
// 此处省略判断销售状态的代码
return productVariantInventoryDTOS;
}
public List<ProductVariantInventoryDTO> getInventoryByVariantIdFromDB(Long shopId, Long productId){
String createProductInventoryKey = CacheKeyGenerateUtil.generateCreateProductInventoryKey(shopId, productId);
//获取构建商品库存的锁,调用构建商品库存的方法,如果50ms没有获取到redis锁,则直接返回null
boolean success = distributedLock.lock(createProductInventoryKey, 50, 5, 10);
if(success){
try{
//二次检查
List<ProductVariantInventoryDTO> productVariantInventoryDTOS = productInventoryCache.getProductVariantInventoryDTOFromCache(shopId ,productId);
// 第二次判空
if(productVariantInventoryDTOS != null){
return productVariantInventoryDTOS;
}
ProductInventoryQuery query = new ProductInventoryQuery();
query.setShopId(shopId);
query.setProductId(productId);
List<ProductVariantInventoryDTO> productInventories = inventoryMapper.getInventoryInfo(query);
//如果查出来是空的也要缓存起来,防止缓存穿透,如果对商品库存有编辑的话,需要在编辑接口中删除并重构缓存
productInventoryCache.setProductVariantInventoryDTO(shopId, productId, productInventories);
return productInventories;
}finally{
distributedLock.releaseLock(createProductInventoryKey);
}
}else{
//如果获取redis锁超时,则直接返回null
throw ShoplusProductException.GET_PRODUCT_INVENTORY_LOCK_FAIL.format(shopId, productId);
}
}
六. 为什么获取库存的时候要用分布式锁?
答:因为从缓存获取库存信息失败,就要去数据库查,查出来肯定要写回缓存。那么100个线程过来查数据库,就要100个线程查完后写入缓存?不需要如此。我们只需要让1个线程从数据库查好了就写回缓存里面。所以需要加锁,至于为什么现在就用用分布式锁,其实就是为了以后分布式架构准备的(现在是单节点,当做分布式时,我们就无需再更改用分布式锁了)。
七. 为什么要做两个判null?
第一次判null是在第一次从缓存获取库存后做的,第二次判null是获得redis分布式锁之后做的。
两次判null的原因类似于单例模式懒加载的双重检查锁定机制。详情可以看单例模式之懒加载模式。
八. Redis分布式锁
还没了解,后期研究完再补充