当前位置:   article > 正文

校园课程助手【8】-RabbitMQ实现异步选课

校园课程助手【8】-RabbitMQ实现异步选课

本节是此项目核心问题,保证在高并发情况下选课业务能够高效、正确的完成。

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);
        });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

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.#");
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

4.消息发送


@Service
@Slf4j
public class MQSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //发送秒杀信息
    public void sendSeckillMessage(String msg){
        log.info("发送"+msg);
        rabbitTemplate.convertAndSend("seckillExchange","seckill.msg",msg);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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;
        }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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("客户端请求错误");
            }
        });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
    //获取抢课结果,成功返回订单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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/939036
推荐阅读
相关标签
  

闽ICP备14008679号