秒杀商城项目-----秒杀接口优化

秒杀接口优化

接口优化核心思路:减少数据库的访问。(数据库抗并发的能力有限)

  1. 使用Redis预减库存减少对数据库的访问
  2. 使用内存标记减少Redis的访问
  3. 使用RabbitMQ队列缓冲,异步下单,增强用户体验

一. 秒杀接口优化

①. 系统初始化,把商品库存数量加载到Redis上面来

秒杀商城项目-----秒杀接口优化

    // afterPropertiesSet系统初始化时,进行一些操作
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVO> list = goodsService.getGoodsVO();
        if (list == null) {
            return;
        }
        for (GoodsVO goodsVO : list) {   // 删除旧缓存
            redisService.delete(GoodsPrefix.goodsMiaoshaStock, "" + goodsVO.getId());
        }
        for (GoodsVO goodsVO : list) {   //缓存预热
            redisService.set(GoodsPrefix.goodsMiaoshaStock, "" + goodsVO.getId(), goodsVO.getStockCount());
            localOverMap.put(goodsVO.getId(), false); //初始化 存储false
        }
    }

秒杀商城项目-----秒杀接口优化

②. 验证path,没有则非法请求

秒杀商城项目-----秒杀接口优化

③. 通过goodsId判断秒杀是否结束,失败直接返回,减少redis访问

秒杀商城项目-----秒杀接口优化

④. 判断缓存中是否已经有订单了

秒杀商城项目-----秒杀接口优化

⑤. 预减库存 redis库存减一,返回剩余库存

秒杀商城项目-----秒杀接口优化

⑥. 交给RabbitMQ进行处理,入队,通过队列把同步请求变为异步请求,减少等待时间

  • 库存充足,且无重复秒杀,将秒杀请求封装后放入消息队列,同时给前端返回一个字符串"排队中",即代表正在排队中(返回的并不是失败或者成功,此时还不能判断)

秒杀商城项目-----秒杀接口优化

        // ⑤.入队,通过队列把同步请求变为异步请求,减少等待时间
        MiaoshaMessageDTO miaoshaMessageDTO = new MiaoshaMessageDTO();
        miaoshaMessageDTO.setGoodsId(goodsId);
        miaoshaMessageDTO.setUser(user);
        //   【减库存 下订单 写入秒杀订单】 全在mq消息队列里面做了
        mqSender.sendMessage(miaoshaMessageDTO);
        //  异步返回排队中
        return ResultUtil.success("排队中");

封装MQ入队信息类

秒杀商城项目-----秒杀接口优化

@Data
public class MiaoshaMessageDTO {
    //用户信息
    private User user;
    //商品ID
    private Long goodsId;
}

⑦. RabbitMQ处理异步订单 ——Rabbit配置

1. 导包

秒杀商城项目-----秒杀接口优化

        <!--消息队列-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2. application.yml配置

秒杀商城项目-----秒杀接口优化

3. Rabbitmq配置类_队列_交换机

秒杀商城项目-----秒杀接口优化

package com.xizi.miaosha.rabbitmq;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xizizzz
 * @description: 消息队列配置类
 * @date 2021-6-23下午 07:48
 */

@Configuration
public class MQConfig {

    //秒杀队列名
    public static final String MIAOSHA_QUEUE = "miaosha.queue";

    //秒杀交换机
    public static final String MIAOSHA_EXCHANGE = "miaosha.exchange";

    //队列注入ioc容器中去
    @Bean
    public Queue queue() {
        return new Queue(MQConfig.MIAOSHA_QUEUE, true);
    }
}

4. 发送信息组件

秒杀商城项目-----秒杀接口优化

package com.xizi.miaosha.rabbitmq;

import com.alibaba.fastjson.JSON;
import com.xizi.miaosha.dto.MiaoshaMessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author xizizzz
 * @description:
 * @date 2021-6-23下午 07:48
 */

@Slf4j
@Component
public class MQSender {

    //注入默认的 amqp模板
    @Resource
    private AmqpTemplate amqpTemplate;

    //发送消息方法
    public void sendMessage(MiaoshaMessageDTO miaoshaMessageDTO) {
        //打印日志
        log.info("【MQ请求入队】,message={}", miaoshaMessageDTO.toString());
        amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, JSON.toJSONString(miaoshaMessageDTO));
    }
}

5. 接收者监听组件

秒杀商城项目-----秒杀接口优化

⑧. 前端接收到数据后,显示排队中,并根据商品id和用户id轮询请求服务器(200ms轮询一次)

1. 请求获取订单接口

秒杀商城项目-----秒杀接口优化

2. 后台商品id和用户id判断缓存中是否有秒杀订单

轮询查询秒杀结果,成功:orderId 失败:-1 排队中:0

秒杀商城项目-----秒杀接口优化

   // 轮询查询秒杀结果
    // 成功:orderId 失败:-1 排队中:0
    @GetMapping(value = "/result")
    @ResponseBody
    public ResultVO result(@RequestParam(value = "goodsId") Long goodsId, User user) {
        //先判断用户
        if (user == null) {
            return ResultUtil.error(ResultEnum.SESSION_OVERDUE);
        }
        //根据用户id和商品id判断 订单的结果
        Long result = miaoshaOrderService.getMiaoshaResult(user.getId(), goodsId);
        return ResultUtil.success(result);
    }

根据用户id和商品id查缓存中 判断订单的结果

秒杀商城项目-----秒杀接口优化

直接从redis中根据用户id和商品id h获取订单的信息

秒杀商城项目-----秒杀接口优化

⑨. 后端RabbitMQ监听秒杀MIAOSHA_QUEUE队列

  • 如果有消息过来就获取到传入的信息,执行真正的秒杀之前,要判断数据库的库存,判断是否重复秒杀,然后执行秒杀事务(秒杀事务是一个原子操作:库存减1,下订单、写入订单详情)
  • 此时,前端根据商品id轮询请求result接口查看是否生成了商品订单,如果返回-1代表秒杀失败,返回0代表排队中,返回>0代表秒杀成功

1. Rabbitmq接收者监听

秒杀商城项目-----秒杀接口优化

2. miaosha关键业务处理

秒杀商城项目-----秒杀接口优化

    //秒杀操作是一个事务,需使用@Transactional注解来标识,如果减少库存失败,则回滚
    @Transactional
    public OrderInfo miaosha(User user, GoodsVO goods) {
        //1. 减库存---更新数据库中的数据
        int i = miaoshaGoodsService.reduceStockById(goods.getId());
        if (i == 0) { //返回更新i=0
            // 秒杀结束 redis存入标记
            setGoodsOver(goods.getId());
            // 抛出自定义异常 秒杀结束
            throw new CustomException(ResultEnum.MIAOSHA_OVER);
        }
        // 2. 根据用户信息和商品信息创建订单
        OrderInfo orderInfo = orderInfoService.createOrder(user, goods);
        // 3. 将订单信息存入到redis中 前端在定时器不断轮询查询订单的结果
        redisService.set(MiaoshaOrderPrefix.getByUserIdAndGoodsId, "" + user.getId() + "_" + goods.getId(), orderInfo);
        //4. 返回订单信息
        return orderInfo;
    }

3. 减库存—更新数据库中的数据

秒杀商城项目-----秒杀接口优化
秒杀商城项目-----秒杀接口优化

public interface MiaoshaGoodsMapper extends Mapper<MiaoshaGoods> {
    //更新数据库中减库存1  并且stock_count>0
    @Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
    public int reduceStockById(@Param("goodsId") Long goodsId);
}

4. 创建订单

秒杀商城项目-----秒杀接口优化

    //创建订单
    @Transactional
    public OrderInfo createOrder(User user, GoodsVO goodsVO) {
        //创建订单信息表
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCreateDate(new Date());
        orderInfo.setDeliveryAddrId(0L);
        orderInfo.setGoodsCount(1);
        orderInfo.setGoodsId(goodsVO.getId());
        orderInfo.setGoodsName(goodsVO.getGoodsName());
        orderInfo.setGoodsPrice(goodsVO.getMiaoshaPrice());
        orderInfo.setUserId(user.getId());
        orderInfo.setOrderChannel(1);
        orderInfo.setStatus(PayStatusEnum.CREATE_NOT_PAY.getCode());
        //插入到订单信息表中
        orderInfoMapper.insertOrderInfo(orderInfo);
        //创建秒杀订单
        MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
        miaoshaOrder.setGoodsId(goodsVO.getId());
        miaoshaOrder.setUserId(user.getId());
        miaoshaOrder.setOrderId(orderInfo.getId() );
        //插入到秒杀订单中
        miaoshaOrderService.createOrder(miaoshaOrder);
        //返回订单信息
        return orderInfo;
    }

5. 将订单信息存入到redis中 前端在定时器不断轮询查询订单的结果

秒杀商城项目-----秒杀接口优化

// 3. 将订单信息存入到redis中 前端在定时器不断轮询查询订单的结果
        redisService.set(MiaoshaOrderPrefix.getByUserIdAndGoodsId, "" + user.getId() + "_" + goods.getId(), orderInfo);
上一篇:Spring框架中的设计模式(三)


下一篇:Activiti7 入门篇