1.订单交易流程分析
订单交易作为涉及到钱的部分势必要谨慎,我们来大体分析一下其中的流程。
- 检查(把不必要的请求第一时间挡回去)
- 检查本地缓存是否售罄
- 校验是否有权限购买
- 判断redis库存是否充足
- 检查是否排队当中
- .... ...
- 获取产品信息
- 验证秒杀或活动时间是否超时
- 获取会员信息
- 获取会员地址
- 预减库存
- 生成下单商品信息
- 库存处理
下面主要分析一下最重要的1,6和8项。
2.检查
- 在检查中最主要的就是检查我们redis中的库存是否充足。
private CommonResult confirmCheck(Long productId, Long memberId, String token){
Integer stock = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId,Integer.class);
if(stock == null||stock<0){
return CommonResult.failed("商品已经售罄,请购买其它商品!");
}
String async = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId + ":" + productId);
if (async != null && async.equals("1")) {
Map<String, Object> result = new HashMap<>();
result.put("orderStatus", "1");//下单方式0->同步下单,1->异步下单排队中,-1->秒杀失败,>1->秒杀成功(返回订单号)
return CommonResult.failed(result, "异步下单排队中");
}
return CommonResult.success(null);
}
在此我们发现我们需要先把商品的库存同步到redis中,否则我们在检查的时候就会永远无库存了。
public void afterPropertiesSet() throws Exception {
FlashPromotionParam promotion = flashPromotionProductDao.getFlashPromotion(null);
if (null==promotion){
return;
}
Date now = new Date();
Date endDate = promotion.getEndDate();//结束时间
final Long expired = endDate.getTime()-now.getTime();//剩余时间
promotion.getRelation().stream().forEach((item)->{
redisOpsUtil().setIfAbsent(
RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + item.getProductId()
, item.getFlashPromotionCount()
, expired
, TimeUnit.MILLISECONDS);
});
}
将库存放入redis中并设置超时时间,活动结束即失效。
- 本地缓存
我们都习惯在后端设置本地缓存来减轻redis的压力,同样在库存检测的时候我们怎么设置本地缓存呢?
当然不需要向redis一样去存库存这样麻烦,我们仅需要一个布尔值的标志位即可,有库存即为true,无库存则为false,如果为false就不在需要去查redis了,即可大大减轻我们redis的负载。但是有一个问题,在集群环境下如何做到同步各节点的数据呢?
用消息队列进行同步就有点大材小用了,这里推荐两种方式,一种是用zk的watcher机制进行同步,另一种就是利用redis的channel机制了,在这里我们用后者进行实现。
首先我们更新下我们上面的代码,加入本地缓存校验的内容
private CommonResult confirmCheck1(Long productId, Long memberId, String token){