改进的点
- 之前的秒杀下单接口被脚本不停的刷
- 秒杀验证逻辑和秒杀下单接口强关联,代码冗余度高
- 秒杀验证逻辑复杂,对交易系统产生无关联复杂
秒杀令牌
//申城秒杀令牌
//校验用户以及商品
@Override
public String generateSecondKillToken(Integer promoId,Integer itemId,Integer userId) {
//获取对应商品的秒杀活动信息
PromoDO promoDO = promoDOMapper.selectByPrimaryKey(promoId);
//dataobject->model
PromoModel promoModel = convertFromDataObject(promoDO);
if(promoModel == null){
return null;
}
//判断当前时间是否秒杀活动即将开始或正在进行,若不是无法生成令牌
if(promoModel.getStartDate().isAfterNow()){
promoModel.setStatus(1);
}else if(promoModel.getEndDate().isBeforeNow()){
promoModel.setStatus(3);
}else{
promoModel.setStatus(2);
}
//
//使用缓存校验itemModel
ItemModel itemModel = itemService.getItemByIdInCache(itemId);
if(itemModel == null){
return null;
}
//判断用户是否存在
UserModel userModel = userService.getUserByIdInCache(userId)
if(userModel == null){
return null;
}
//生成tockey存入redis内,并给一个5分钟的有效期
if(promoModel.getStatus().intValue()!=2){
return null;
}else{
//获取秒杀大闸的count数量
long result = redisTemplate.opsForValue().increment("promo_door_count_" + promoId,-1);
if(result<0) return null;
//生成一个秒杀令牌
String token = UUID.randomUUID().toString().replace("-","");
redisTemplate.opsForValue().set("promo_token_"+promoId+"_userid_"+userId+"_itemid_"+itemId,token);
redisTemplate.expire("promo_token_"+promoId+"_userid_"+userId+"_itemid_"+itemId,5, TimeUnit.MINUTES);
return token;
}
}
秒杀大闸
//限制秒杀令牌的数量:多少个用户可以获取到秒杀令牌
//将秒杀大闸限制到redis内
redisTemplate.opsForValue().set("promo_door_count_"+promoId,itemModel.getStock().intValue()*4);
队列泄洪(拥塞窗口)
在orderController中有用户下单时,使用线程池,从线程池中选取线程进行操作,每次
最多有20个用户
可以进行下单的操作
//同步调用线程池的submit方法
//同步调用线程池的subnmit方法
//拥塞窗口为20的等待队列,用来队列化泄洪
Future<Object> future = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
//加入库存流水init状态
String stockLogId = itemService.initStockLog(itemId,amount);
if(!mqProducer.transactionAsyncReduceStock(userModel.getId(),promoId,itemId,amount,stockLogId)){
throw new BusinessException(EmBusinessError.UNKNOWN_ERROR,"下单失败");
}
return null;
}
});