秒杀项目(6)接口优化

一、接口优化过程

1.1 过程要点

  • Redis预减库存减少数据库访问
  • 内存标记减少Redis访问
  • 请求先入队缓冲,异步下单,增强用户体验。
  • RabbitMQ安装与Spring Boot继承
  • Nginx水平扩展
  • 分库分表中间件 mycat

二、具体实现

2.1 减少数据库的访问

  • 系统初始化,商品库存数量添加到Redis中
  • 收到请求,Redis预减库存,库存不足,直接返回,否则进入3
  • 请求入队,立即返回排队中
  • 请求出队,生成订单,减少库存
  • 客户端轮询是否秒杀成功

2.2 系统初始化,商品库存数量添加到Redis中

需要实现InitializingBean 接口,重写afterPropertiesSet()方法

public class miaoshaController implements InitializingBean {
    @Autowired
    GoodsService goodsService;
    @Autowired
    OrderService orderService;
    @Autowired
    MiaoshaService miaoshaService;
    @Autowired
    OrderDao orderDao;
    @Autowired
    RedisService redisService;

    //系统初始化做的事情
    @Override
    public void afterPropertiesSet() throws Exception {
        //系统启动的时候把商品的数量加载进内存
        List<GoodsVo> goodsVos = goodsService.selectAll();
        if(goodsVos.isEmpty()){
            return;
        }
        for(GoodsVo goodsVo : goodsVos){
            redisService.set(GoodsKey.ByGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount());
        }
    }
}

2.3 判断用户是否登录

  if(miaoshaUser == null){
            return Result.error(CodeMsg.SESSION_ERROR);
        }

2.4 预减库存

原来是直接去数据库查询库存是否足够秒杀,秒杀则直接秒杀。现在是在Redis中找到商品的库存信息,进行预减库存。

 //预减库存
        Long count = redisService.decr(GoodsKey.ByGoodsStock,""+goodsId);
        if(count < 0){
            model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
            return "miaosha_fail";
        }

2.5 判断是否重复秒杀

根据商品的ID和当前用户登录的ID从换缓存中和数据库中查询,看是否生成订单,来判断是否重复秒杀

GoodsVo goodsVo = goodsService.seleteReferGoods(goodsId);
        OrderInfo orderInfo = null;
        OrderInfo orderInfo1 = redisService.get(MiaoshaOrderKey.OrderByManyID, "" + miaoshaUser.getId() + goodsVo.getId(), OrderInfo.class);
        if(orderInfo1 != null){
            model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
            return Result.error(CodeMsg.MIAOSHA_FAIL);

        }else{
            orderInfo = orderService.getOrderInfo(miaoshaUser.getId(),goodsId);
        }
        if(orderInfo != null){
            model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
            return Result.error(CodeMsg.MIAOSHA_FAIL);
        }

三、秒杀和RabbitMq结合

3.1 秒杀信息对应的实体类

public class MiaoshaMsg {
    private MiaoshaUser user;
    private long goodsId;
    }

3.2 秒杀信息入队

先将秒杀信息转换为JSO格式的字符串,然后入队,秒杀实现在消息队列的服务端

@Service
public class RabbitMqSend {
    @Autowired
    AmqpTemplate amqpTemplate;
    @Autowired
    RedisService redisService;
    public void send(MiaoshaMsg miaoshaMsg){
        String messgae = redisService.BeanToString(miaoshaMsg);
        amqpTemplate.convertAndSend(MqConfig.Queue1,messgae);
    }
}

3.3 消息队列服务端实现秒杀

  • 判断库存是否足够
  • 判断是否重复秒杀
  • 减库存,下订单
  • 秒杀成功,将秒杀成功信息写到redis中
 @RabbitListener(queues = MqConfig.Queue1)
    public String Receive(String str, Model model){
        MiaoshaMsg miaoshaMsg = redisService.StringToBean(str, MiaoshaMsg.class);
        long goodsId = miaoshaMsg.getGoodsId();
        MiaoshaUser miaoshaUser = miaoshaMsg.getUser();
        //1.判断库存是否足够
        GoodsVo goodsVo = goodsService.seleteReferGoods(goodsId);
        Integer stockCount = goodsVo.getStockCount();
        if(stockCount <= 0){
            model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
            return "miaosha_fail";
        }
        //2.判断是否重复秒杀
        //3.判断是否重复秒杀
        //先从缓存中取
        OrderInfo orderInfo = null;
        OrderInfo orderInfo1 = redisService.get(MiaoshaOrderKey.OrderByManyID, "" + miaoshaUser.getId() + goodsVo.getId(), OrderInfo.class);
        if(orderInfo1 != null){
            model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
            return "miaosha_fail";

        }else{
            orderInfo = orderService.getOrderInfo(miaoshaUser.getId(),goodsId);
        }
        if(orderInfo != null){
            model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
            return "miaosha_fail";
        }
        //System.out.println("步骤3");
        //4.减库存,下订单,写入秒杀订单,需要在事务中操作
        OrderInfo miaoshaOrderInfo = miaoshaService.RealMiaosha(miaoshaUser, goodsVo);
        model.addAttribute("orderinfo",miaoshaOrderInfo);
        model.addAttribute("goods",goodsVo);
        //System.out.println("步骤4");
        //5.秒杀成功,存入redis中
        redisService.set(MiaoshaOrderKey.OrderByManyID,""+miaoshaUser.getId() + goodsVo.getId(),orderInfo);
        log.info("ID是: " + miaoshaMsg.getUser().getId() + "的用户秒杀了商品ID为" + miaoshaMsg.getGoodsId() + "的商品" );
        return "order_detail";
    }

3.4 秒杀时做一个内存标记

作用:提示商品已经秒杀光了,这样服务端在处理消息时就很可以直接返回秒杀失败。
实现:通过判断数据库减少一个数量后,商品的库存是否为负数来判断

 //减少库存,Goods表的库存得减少,MiaoshaGoods表的库存也得减少
        OrderInfo orderInfo = null;
        if( goodsVo.getStockCount()<= 0){
            setGoodsOver(goodsVo.getId());
        }

3.5 需要在前端轮询秒杀的结果,对应的后端是

通过result来返回处理的结果:

 @RequestMapping(value = "/result",method = RequestMethod.GET)
    @ResponseBody
    public Result<Long> miaosharesult(Model model,MiaoshaUser miaoshaUser,@RequestParam("godsId")Long goodsId){
        model.addAttribute("user",miaoshaUser);
        if(miaoshaUser == null){
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        long result = miaoshaService.getMiaoshaResult(miaoshaUser.getId(),goodsId);
        return Result.success(result);
    }

秒杀成功返回商品的Id,失败返回-1,正在处理返回0;

 //秒杀成功返回商品Id
    //失败返回-1
    //正在处理返回0
    public long getMiaoshaResult(Long id, Long goodsId) {
        MiaoshaOrder miaoshaOrder = miaoshaOrderService.selectByUserIDGoodsID(id, goodsId);
        if(miaoshaOrder != null){
            return miaoshaOrder.getOrderId();
            //一种情况秒杀失败,一种是正在处理
        }else {
            boolean isOver = getGoodsOver(goodsId);
            if(isOver){
                //商品是不是秒杀完
                return -1;
            }else {
                //没有就继续轮询
                return 0;
            }
        }
    }

3.7 访问redis优化

在每次的业务逻辑,都需要去redis中取库存的信息,即使库存不足,也要去redis中访问库存的信息,内存标志的作用就是使库存不足时,就不去redis中预减库存了。

系统初始化的时候设置一个标志,存到map中

  for(GoodsVo goodsVo : goodsVos){
            redisService.set(GoodsKey.ByGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount());
            map.put(goodsVo.getId(),false);
        }

预减库存之前先去map中判断标志

Boolean flag = (Boolean) map.get(goodsId);
        if(flag){
            return Result.error(CodeMsg.MIAOSHA_OVER_ERROR);
        }

库存不足,设置标志

if(count < 0){
            map.put(goodsId,true);
            model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
            return Result.error(CodeMsg.MIAOSHA_FAIL);
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值