秒杀实战——高并发优化

1.使用缓存

页面缓存+URL缓存+对象缓存

使用这些缓存的优点:
1.减轻数据库压力(核心)
2.提高用户体验
3.增强系统的并发能力

(1)页面缓存

	@ApiOperation(httpMethod = "POST", value = "商品列表", notes = "查询所有商品")
	    @ApiImplicitParams({
	            @ApiImplicitParam(name = "id", value = "商品id", required = true, dataType = "Long")
	    })
	    @RequestMapping(value = "toList", produces = "text/html")
	    @ResponseBody
	    public String toList(
	            HttpServletRequest request, HttpServletResponse response,
	            Model model, MiaoshaUser user) {
	        model.addAttribute("user", user.toString());
	        List<GoodsVO> goodsList = goodsService.listGoodsVo();
	        model.addAttribute("goodsList", goodsList);
	        //取缓存
	        String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
	        if (!StringUtils.isEmpty(html)) {
	            return html;
	        }
	        
	        WebContext ctx = new WebContext(request, response,
	                request.getServletContext(), request.getLocale(), model.asMap());
	        //手动渲染
	        html = thymeleafViewResolver.getTemplateEngine().process("goodsList", ctx);
	        if (!StringUtils.isEmpty(html)) {
	            redisService.set(GoodsKey.getGoodsList, "", html);
	        }
	        return html;
	    }

新建一个GoodsKey的类:

public class GoodsKey extends BasePrefix{

    public GoodsKey(int expireSeconds, String prefix) {
        super(expireSeconds, prefix);
    }
    public static GoodsKey getGoodsList = new GoodsKey(60,"goodsList");
    public static GoodsKey getGoodsDetail = new GoodsKey(60,"goodsDetail");

}

(2)URL缓存

截取片段如下:

    @ApiOperation(httpMethod = "POST", value = "商品详情", notes = "某一个商品详情")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "goodsId", value = "商品id", required = true, dataType = "Long")
    })
    @RequestMapping(value = "toDetail/{goodsId}", produces = "text/html")
    @ResponseBody
    public String toDetail(
            HttpServletRequest request, HttpServletResponse response,
            Model model, MiaoshaUser user,
            @PathVariable("goodsId") long goodsId) {
//snowflake
        //取缓存
        String html = redisService.get(GoodsKey.getGoodsList, "" + goodsId, String.class);
        if (!StringUtils.isEmpty(html)) {
            return html;
        }
        logger.info("id:" + goodsId);
        GoodsVO goodsVO = goodsService.getGoodsVoByGoodsId(goodsId);
        model.addAttribute("goods", goodsVO);
        model.addAttribute("user", user.toString());
        logger.info("goods:" + goodsVO);
        //开始 结束 时间
        long startAt = goodsVO.getStartDate().getTime();
        long endAt = goodsVO.getEndDate().getTime();
        long now = System.currentTimeMillis();
        logger.info("now:" + now + "\t" + "startAt:" + startAt + "\t" + "endAt:" + endAt);
        //开始状态
        int miaoshaStatus = 0;
        //时间
        int remainSeconds = 0;
        //秒杀倒计时 开始 结束 进行中
        if (now < startAt) {
            remainSeconds = (int) ((startAt - now) / 1000);
        } else if (now > endAt) {
            miaoshaStatus = 2;
            remainSeconds = -1;
        } else {
            miaoshaStatus = 1;
        }
        logger.info("miaoshaStatus:" + miaoshaStatus + "\t" + "remainSeconds:" + remainSeconds);
        model.addAttribute("miaoshaStatus", miaoshaStatus);
        model.addAttribute("remainSeconds", remainSeconds);
//        return "goodsDetail";

        //手动渲染
        WebContext ctx = new WebContext(request, response,
                request.getServletContext(), request.getLocale(), model.asMap());
        //手动渲染
        html = thymeleafViewResolver.getTemplateEngine().process("GoodsDetail", ctx);
        if (!StringUtils.isEmpty(html)) {
            redisService.set(GoodsKey.getGoodsDetail, "" + goodsId, html);
        }
        return html;
    }

(3)对象缓存

截取片段如下:

 public MiaoshaUser getById(long id) {
        // 取缓存
        MiaoshaUser user = redisService.get(MiaoshaUserKey.getById,""+id,MiaoshaUser.class);
        if (user != null ){
            return user;
        }
        // 取数据库,并存入缓存
        user = miaoshaUserDao.getById(id);
        if (user != null ){
            redisService.set(MiaoshaUserKey.getById,""+id, user);
        }
        return user;
    }

MiaoshaUserKey:

public class MiaoshaUserKey extends BasePrefix{

    private static final int TOKEN_EXPIRE = (3600 * 24 * 2);

    public MiaoshaUserKey(String prefix) {
        super(prefix);
    }

    public MiaoshaUserKey(int expireSeconds, String prefix) {
        super(expireSeconds, prefix);
    }

    public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE,"tk");

    public static MiaoshaUserKey getById = new MiaoshaUserKey(0,"id");

}

2.页面静态化

页面静态化的优点:

  1. 提高速度
    比如jsp这一类的动态网页,需要进行数据库查询,这时访问量增加,数据库查询的次数也会随之增加,会占用很大的资源,影响到网站的反应速度。如果把首页、内容静态化的话,就会去除了查询数据库的次数,减少一部分环节,加快网站反应速度。
    2.搜索引擎的收录
    从网站的优化上来说,搜索引擎更喜欢静态网页,也容易进行抓取,SEO排名也会更高。比如百度、阿里巴巴、搜狐都使用静态网页或者伪静态网页来显示,方便搜索引擎的抓取与排名。
    3.安全性
    静态网页不容易被黑客所攻破。静态网页没有查询数据库,不会让黑客看到数据库里的内容。
    4.网站稳定性
    如果后台程序、数据库等出现错误,影响网站的访问,影响用户的体验度,降低了用户的信任度,而静态网页就不会出现这种情况。

(1)商品详情页静态化

1.GoodsController:

/**
     * 商品详情页面静态化
     */
    @ApiOperation(httpMethod = "POST", value = "商品详情", notes = "某一个商品详情")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "goodsId", value = "商品id", required = true, dataType = "Long")
    })
    @RequestMapping(value = "to_detail2/{goodsId}")
    @ResponseBody
    public Result<GoodsDetailVO> toDetail2(
            HttpServletRequest request, HttpServletResponse response,
            Model model, MiaoshaUser user,
            @PathVariable("goodsId") long goodsId) {

        GoodsVO goodsVO = goodsService.getGoodsVoByGoodsId(goodsId);
        logger.info("-------goodsVO:" + goodsVO);
        //开始 结束 时间
        long startAt = goodsVO.getStartDate().getTime();
        long endAt = goodsVO.getEndDate().getTime();
        long now = System.currentTimeMillis();
        logger.info("now:" + now + "\t" + "startAt:" + startAt + "\t" + "endAt:" + endAt);
        //开始状态
        int miaoshaStatus = 0;
        //时间
        int remainSeconds = 0;
        //秒杀倒计时 开始 结束 进行中
        if (now < startAt) {
            remainSeconds = (int) ((startAt - now) / 1000);
        } else if (now > endAt) {
            miaoshaStatus = 2;
            remainSeconds = -1;
        } else {
            miaoshaStatus = 1;
        }
        GoodsDetailVO vo = new GoodsDetailVO();
        vo.setGoods(goodsVO);
        vo.setUser(user);
        vo.setRemainSeconds(remainSeconds);
        vo.setMiaoshaStatus(miaoshaStatus);
        return Result.success(vo);
    }

2.新建一个GoodsDetailVO工具类:

@Data
public class GoodsDetailVO implements Serializable {
    //秒杀状态
    private int miaoshaStatus = 0;
    //剩余时间时间
    private int remainSeconds = 0;

    private GoodsVO goods;

    private MiaoshaUser user;

    public int getMiaoshaStatus() {
        return miaoshaStatus;
    }

    public void setMiaoshaStatus(int miaoshaStatus) {
        this.miaoshaStatus = miaoshaStatus;
    }

    public int getRemainSeconds() {
        return remainSeconds;
    }

    public void setRemainSeconds(int remainSeconds) {
        this.remainSeconds = remainSeconds;
    }

    public GoodsVO getGoods() {
        return goods;
    }

    public void setGoods(GoodsVO goods) {
        this.goods = goods;
    }

    public MiaoshaUser getUser() {
        return user;
    }

    public void setUser(MiaoshaUser user) {
        this.user = user;
    }


}

3.在goodsList.html中做出如下修改

<!--<td><a th:href="'/goods/toDetail/'+${goods.id}">详情</a></td>-->

<td><a th:href="'/goodsDetail.htm?goodsId='+${goods.id}">详情</a></td>

4.新建一个goodsDetail.htm页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>商品详情页</title>
</head>
<!-- jquery -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css"/>
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
<!-- jquery-validator -->
<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
<!-- layer -->
<script type="text/javascript" src="/layer/layer.js">

</script>
<!-- md5.js -->
<script type="text/javascript" src="/js/md5.min.js"></script>
<!-- common.js -->
<script type="text/javascript" src="/js/common.js"></script>
<body>
<div class="panel panel-default">
    <div class="panel-heading">秒杀商品详情</div>
    <div class="panel-body">
        <span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>
        <span>没有收货地址的提示!</span>
    </div>
    <table class="table" id="goodslist">
        <tr>
            <td>商品名称</td>
            <td colspan="3" id="goodsName"></td>
        </tr>
        <tr>
            <td>商品图片</td>
            <td colspan="3"><img id="goodsImg" width="200" height="200"/></td>
        </tr>
        <tr>
            <td>秒杀开始时间</td>
            <td id="startTime"></td>
            <td>
                <input type="hidden" id="remainSeconds"/>
                <span id="miaoshaTip"></span>
            </td>
            <td>

                <button class="btn btn-primary btn-block" type="button" id="buyButton" onclick="doMiaosha()">立即秒杀</button>
                <input type="hidden" name="goodsId" id="goodsId"/>
            </td>
        </tr>
        <tr>
            <td>商品原价</td>
            <td colspan="3" id="goodsPrice"></td>
        </tr>
        <tr>
            <td>秒杀价</td>
            <td colspan="3" id="miaoshaPrice"></td>
        </tr>
        <tr>
            <td>库存数量</td>
            <td colspan="3" id="stockCount"></td>
        </tr>
    </table>
</div>
<script>
    function doMiaosha(){

    }
    $(function () {
        getDetail();
    })

    function getDetail() {
        //获取url参数
        const goodsId = g_getQueryString("goodsId");
        $.ajax({
            url: "/goods/to_detail2/" + goodsId,
            type: "POST",
            success: function (data) {
                if (data.code === 0) {
                    // layer.msg("渲染页面")
                    //渲染页面
                    render(data.data);
                } else {
                    layer.msg(data.msg());
                }
            },
            error: function (data) {
                layer.msg("客户端请求有误")
            }

        })
    }

    /**
     * 渲染页面信息
     */
    function render(detail) {
        const miaoshaStatus = detail.miaoshaStatus;
        const remainSeconds = detail.remainSeconds;
        const user = detail.user;
        const goods = detail.goods;
        if (user) {
            //如果user存在,说明已经登录,不再显示
            $("#userTip").hide();
        }
        //填充页面数据
        if (goods) {
            $("#goodsName").text(goods.goodsName);
            $("#goodsImg").attr("src", goods.goodsImg);
            $("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
            $("#remainSeconds").val(remainSeconds);
            $("#goodsId").val(goods.id);
            $("#goodsPrice").text(goods.goodsPrice + "元");
            $("#miaoshaPrice").text(goods.miaoshaPrice + "元");
            $("#stockCount").text(goods.stockCount);
        }
        countDown();
    }

    function countDown() {
        const remainSeconds = $("#remainSeconds").val();
        let timeout;
        //秒杀还没开始,倒计时
        if (remainSeconds > 0) {
            $("#buyButton").attr("disabled", true);
            $("#miaoshaTip").html("秒杀倒计时:" + remainSeconds + "秒");
            timeout = setTimeout(function () {
                $("#countDown").text(remainSeconds - 1);
                $("#remainSeconds").val(remainSeconds - 1);
                countDown();
            }, 1000);
            //秒杀进行中
        } else if (remainSeconds == 0) {
            $("#buyButton").attr("disabled", false);
            if (timeout) {
                clearTimeout(timeout);
            }
            $("#miaoshaTip").html("秒杀进行中");
            //秒杀已经结束
        } else {
            $("#buyButton").attr("disabled", true);
            $("#miaoshaTip").html("秒杀已经结束");
        }
    }
</script>
</body>
</html>

(2)秒杀静态化

MiaoshaController:

@ApiOperation(httpMethod = "POST",value = "秒杀",notes = "立即秒杀")
    @ApiImplicitParam(name = "id",value = "商品id",required = true ,dataType = "Long")
    @RequestMapping("do_miaosha")
    public String doSha(Model model, MiaoshaUser user,
                        @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return "login";
        }
        //判断库存
        GoodsVO goodsVO = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goodsVO.getStockCount();
        if (stock <= 0) {
            model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER.getMsg());
            return "miaosha_fail";
        }
        //判断是否已经秒杀到了
        MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            model.addAttribute("errmsg", CodeMsg.MIAOSHA_REPET.getMsg());
            return "miaosha_fail";
        }
        //减少库存 下订单 写入秒杀订单
        OrderInfo orderInfo = miaoshaService.miaosha(user, goodsVO);
        model.addAttribute("orderInfo", orderInfo);
        model.addAttribute("goods",goodsVO);
        logger.info("goods:"+goodsVO+"\t"+"orderInfo:"+orderInfo);
        return "order_detail";
    }
}

3.静态资源优化

常用的优化手段:
1.JS/CSS压缩,减少流量;
2.多个JS/CSS组合,减少连接数;
3.CDN就近访问。

相关工具扩展:

Tengine

Tengine是由淘宝网发起的Web服务器顶目,它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性, Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验.它的最终目标是打造一个高效、稳定、安全、易用的web平台。

webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

CDN

CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值