赞
踩
本节是此项目核心问题,保证在高并发情况下选课业务能够高效、正确的完成。
1.在进行选课前将课程库存提前加载到Redis中:
//在抢课Controller中实现InitializingBean接口 //初始化时执行将库存预加载到Redis @Override public void afterPropertiesSet() throws Exception { List<CourseVo> list = courseService.findCourseVo(); if (CollectionUtils.isEmpty(list)) { return; } list.forEach(CourseVo-> { redisTemplate.opsForValue().set("seckillCourese:"+CourseVo.getId(),CourseVo.getStockCount()); if(CourseVo.getStockCount() > 0) emptyStockMap.put(CourseVo.getId(),false); else emptyStockMap.put(CourseVo.getId(),true); }); }
2.抢课Controller
//使用前后端分离,对象缓存减少前端页面的数据访问,同时使用Redis判断是否重复抢购 @RequestMapping(value = "/{path}/doSeckill",method = RequestMethod.POST) @ResponseBody public RespBean doSecKill(@PathVariable String path,User user,Long CourseId){ if(user == null) return RespBean.error(RespBeanEnum.SESSION_ERROR); boolean check = orderService.checkPath(user,CourseId,path); if(!check){ return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL); } //判断是否重复抢购 TSeckillOrder seckillOrder = (TSeckillOrder) redisTemplate.opsForValue().get("order:"+user.getId()+":"+CourseId); if(seckillOrder != null){ return RespBean.error(RespBeanEnum.REPEATE_ERROR); } if(emptyStockMap.get(CourseId)){ return RespBean.error(RespBeanEnum.EMPTY_STOCK); } //通过Redis预减库存 ValueOperations valueOperations = redisTemplate.opsForValue(); //原子性预减库存操作 Long stock = valueOperations.decrement("seckillCourse:"+CourseId); //也可以使用Redis结合lua脚本 // Long stock = (Long) redisTemplate.execute(script, Collections.singletonList("seckillCourse:"+CourseId), // Collections.EMPTY_LIST); if(stock < 0){ emptyStockMap.put(CourseId,true); valueOperations.increment("seckillCourse:"+CourseId); return RespBean.error(RespBeanEnum.EMPTY_STOCK); } SeckillMessage seckillMessage = new SeckillMessage(user, CourseId); mqSender.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessage)); // 通过RabbitMQ消息队列下单,0状态表示排队中 return RespBean.success(0); }
3.配置RabbitMQ
@Configuration public class RabbitMQTopicConfig { private static final String QUEUE = "seckillQueue"; private static final String EXCHANGE = "seckillExchange"; @Bean public Queue queue() { return new Queue(QUEUE); } @Bean public TopicExchange topicExchange() { return new TopicExchange(EXCHANGE); } @Bean public Binding binding() { return BindingBuilder.bind(queue()).to(topicExchange()).with("seckill.#"); }
4.消息发送
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
//发送秒杀信息
public void sendSeckillMessage(String msg){
log.info("发送"+msg);
rabbitTemplate.convertAndSend("seckillExchange","seckill.msg",msg);
}
}
5.消息接收
@Service @Slf4j public class MQReceiver { @Autowired private ITCourseService courseService; @Autowired private RedisTemplate redisTemplate; @Autowired private ITOrderService orderService; //接收消息,实际上进行下单操作 @RabbitListener(queues = "seckillQueue") public void receive(String msg){ log.info("接收"+msg); SeckillMessage seckillMessage = JsonUtil.jsonStr2Object(msg, SeckillMessage.class); Long courseId = seckillMessage.getCourseId(); User user = seckillMessage.getUser(); CourseVo courseVobyCourseId= courseService.findCourseVobyCourseId(courseId ); if(courseVobyCourseId.getStockCount() < 1){ return; } //判断是否重复抢购 TSeckillOrder seckillOrder = (TSeckillOrder) redisTemplate.opsForValue().get("order:"+user.getId()+":"+courseId ); if(seckillOrder != null){ return ; } //下单 orderService.secKill(user,courseVobyCourseId); } }
6.选课成功sevice下单逻辑
@Transactional @Override public TOrder secKill(User user, CourseVo CourseVo) { ValueOperations valueOperations = redisTemplate.opsForValue(); //从后端重新查询库存,拿到秒殺商品信息 TSeckillCourse seckillCourse = itSeckillCourseService.getOne(new QueryWrapper<TSeckillCourse>().eq("Course_id", CourseVo.getId())); //库存减一 seckillCourse.setStockCount(seckillCourse.getStockCount() - 1); //id没问题同时库存>0才更新 boolean seckillCourseResult = itSeckillCourseService.update(new UpdateWrapper<TSeckillCourse>() .setSql("stock_count = " + "stock_count-1") .eq("Course_id", CourseVo.getId()) .gt("stock_count", 0) ); if (seckillCourse.getStockCount() < 1) { valueOperations.set("isStockEmapty:"+ CourseVo.getId(),"0"); return null; } //生成订单 TOrder order = new TOrder(); order.setUserId(user.getId()); order.setCourseId(CourseVo.getId()); order.setDeliveryAddrId(0L); order.setCourseName(CourseVo.getCourseName()); order.setCourseCount(1); order.setCoursePrice(seckillCourse.getSeckillPrice()); order.setOrderChannel(1); order.setStatus(0); order.setCreateDate(new Date()); tOrderMapper.insert(order); TSeckillOrder tSeckillOrder = new TSeckillOrder(); tSeckillOrder.setUserId(user.getId()); tSeckillOrder.setOrderId(order.getId()); tSeckillOrder.setCourseId(CourseVo.getId()); itSeckillOrderService.save(tSeckillOrder); //这里使用Redis缓存用户id和订单id作为联合key,在高并发时订单控制器可以先进行查询防止一个人多次 redisTemplate.opsForValue().set("order:" + user.getId() + ":" + CourseVo.getId(), tSeckillOrder, 1, TimeUnit.MINUTES); return order; }
7.前端通过轮询得知抢课是否成功
function doSeckill(path) { $.ajax({ url: '/seckill/'+path+'/doSeckill', type: "POST", data: { courseId: $('#courseId').val() // path:path改用注解接收 }, success: function (data) { if (data.code == 200) //使用RabbitMQ轮询时 getResult($("#courseId").val()); } else { layer.msg(data.message); } }, error: function () { layer.msg("客户端请求出错"); } }); } function getResult(courseId){ g_showLoading(); $.ajax({ url:"/seckill/result", type:"GET", data: { courseId: courseId }, success: function (data) { if (data.code == 200) { var result = data.object; if (result < 0) { layer.msg("对不起,秒杀失败"); } else if (result == 0) { setTimeout(function () { getResult(courseId) },50); } else { layer.confirm("恭喜您,秒杀成功!查看订单?", {btn: ["确定", "取消"]}, function () { window.location.href = "/orderDetail.htm?orderId=" + result; }, function () { layer.close(); } ) } } }, error: function () { layer.msg("客户端请求错误"); } }); }
//获取抢课结果,成功返回订单id,失败-1,正在排队0
@RequestMapping(value = "/result",method = RequestMethod.GET)
@ResponseBody
public RespBean getResult(User user, Long goodsId){
if(user == null){
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
Long orderId = seckillOrderService.getResult(user,goodsId);
return RespBean.success(orderId);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。