第二版

分布式session

对每一次登陆的user用户都生成一个对应的token值放到cookie中,并将token与对应的user放到redis中保存。在对用户进行不同操作时都会重新生成cookie值以此延长cookie的生命周期。

 //生成cookie
String token = UUIDUtil.uuid();   
addCookie(response, token, user);

public void addCookie(HttpServletResponse response, String token, MiaoshaUser user){
    myRedisUtil.set(MiaoshaUserKey.token, token, user); //将user和token绑定并存入Redis中
    Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);   //根据token生成cookie
    cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
    cookie.setPath("/");
    response.addCookie(cookie); //将cookie放入response客户端中
    }

页面缓存技术

将商品列表等这些变化不大的页面以整个页面的形式全部放到redis缓存中。根据实际情况设置一个过期时间,过期后再重新设置缓存。
采用SpringWebContextthymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);对页面进行手动渲染放到redis中。

    @RequestMapping(value = "/to_list", produces = "text/html")
    @ResponseBody
    public String list(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) {
        model.addAttribute("user", user);
        //取缓存
        String html = myRedisUtil.get(GoodsKey.getGoodsList, "", String.class);
        if (!StringUtils.isEmpty(html)){
            return html;
        }
        //查询商品列表,包括商品和秒杀商品
        List<GoodsVo> goodsList = goodsService.listGoodsVo();
        model.addAttribute("goodsList", goodsList);     //放到Model中,供前端展示使用。
//        return "goods_list";
        //手动渲染
        SpringWebContext ctx = new SpringWebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap(), applicationContext);
        html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
        if (!StringUtils.isEmpty(html)){
            myRedisUtil.set(GoodsKey.getGoodsList, "", html);
        }
        return html;
    }

对象缓存

将热点数据进行缓存,更加细粒度的缓存。
比如分布式session,将用户信息中放到redis缓存中。
取缓存——取数据库/更新数据库——更新缓存

    public MiaoshaUser getById(long id){
        //取缓存
        MiaoshaUser miaoshaUser = myRedisUtil.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
        if (miaoshaUser != null){
            return miaoshaUser;
        }
        //取数据库
        miaoshaUser = miaoshaUserDao.getById(id);
        if (miaoshaUser != null){
        //更新缓存
            myRedisUtil.set(MiaoshaUserKey.getById, ""+id, miaoshaUser);
        }
        return miaoshaUser;
    }

接口优化

减少数据库的访问

  1. 系统初始化,将商品库存数量加载到Redis
  2. 收到请求,Redis预减库存,库存不足,直接返回
  3. 放到rabbitmq队列中
  4. 请求出队,生成订单,减少库存

系统初始化,将商品数量加载到redis中

采用afterPropertiesSet方法

    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsList = goodsService.listGoodsVo();
        if (goodsList == null){
            return;
        }
        for (GoodsVo goods:goodsList){
            myRedisUtil.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
            localOverMap.put(goods.getId(), false);
        }
    }

优化步骤

  1. 采用hashmap存放库存是否小于0。如果小于0则为false,返回秒杀失败。
  2. 预减库存:long stock = myRedisUtil.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId); decr方法
  3. 判断是否已经参与秒杀,如果参与秒杀则返回,没有参与秒杀则入队。
@RequestMapping(value = "/do_miaosha", method = RequestMethod.POST)
    @ResponseBody
    public Result<Integer> miaosha(Model model, MiaoshaUser user, @RequestParam("goodsId") long goodsId){
        model.addAttribute("user", user);
        if (user == null){
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //内存标记
        boolean over = localOverMap.get(goodsId);
        if (over){
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }

        //预减库存
        long stock = myRedisUtil.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);
        if (stock < 0){
            localOverMap.put(goodsId, true);
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }

        //判断是否已经秒杀到
        MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (miaoshaOrder != null){
            return Result.error(CodeMsg.REPEATE_MIAOSHA);
        }

        //入队
        MiaoshaMessage miaoshaMessage = new MiaoshaMessage();
        miaoshaMessage.setUser(user);
        miaoshaMessage.setGoodsId(goodsId);
        mqSender.sendMiaoshaMessage(miaoshaMessage);
        return Result.success(0);//排队中

        /*
        //判断秒杀库存
        GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goodsVo.getStockCount();
        if (stock <= 0){
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
        //判断是否已经秒杀到
        MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (miaoshaOrder != null){
            return Result.error(CodeMsg.REPEATE_MIAOSHA);
        }
        //进行秒杀步骤:减库存 创建普通订单 创建秒杀订单    注意这是个事务操作
        OrderInfo orderInfo = miaoshaService.miaosha(user, goodsVo);
        return Result.success(orderInfo);
        */
    }

rabbitmq异步下单

通过direct直连形式监听消费消息。
删减库存,判断是否可以下单。
生成订单。

    @RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)
    public void receive(String message){
        log.info("receive message:" + message);
        MiaoshaMessage miaoshaMessage = MyRedisUtil.stringToBean(message, MiaoshaMessage.class);
        MiaoshaUser user = miaoshaMessage.getUser();
        long goodsId = miaoshaMessage.getGoodsId();

        //判断库存
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10个商品,req1 req2
        int stock = goods.getStockCount();
        if(stock <= 0) {
            return;
        }
        //判断是否已经秒杀到
        MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (miaoshaOrder != null){
            return;
        }
        //减库存 下订单 写入秒杀订单
        miaoshaService.miaosha(user, goods);
    }

安全优化

秒杀接口地址隐藏

  1. 接口改造
  2. 添加生成地址的接口
  3. 秒杀收到请求,先验证
//建立新的秒杀path
    public String createMiaoshaPath(MiaoshaUser user, long goodsId) {
        if (user == null || goodsId <= 0){
            return null;
        }
        String str = MD5Util.md5(UUIDUtil.uuid() + "123456");
        myRedisUtil.set(MiaoshaKey.getMiaoshaPath, "" + user.getId() + "_" + goodsId, str);
        return str;
    }

        //验证path
        boolean check = miaoshaService.checkPath(user, goodsId, path);
        if (!check){
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }

数学公式验证码

点击秒杀之前,先输入验证码,分散用户的请求。

接口限流防刷

将访问次数放到redis中

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。