秒杀优化

1、页面缓存

当客户的请求到达后端时,先去redis中查询缓存,如果缓存中找不到,则进行数据库逻辑操作,然后渲染,存入缓存并返回给前端,如果在缓存中找到了则直接返回给前端。

采用页面缓存,后端人员可以将返回给客户端的页面,提前渲染页面存于redis缓存中。

1、取缓存

2、手动渲染模板

3、结果输出

对商品列表进行页面缓存

   @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 = redisService.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)){
            redisService.set(GoodsKey.getGoodsList, "", html);
        }
        return html;
    }

GoodsKey :作为页面缓存的缓存Key的前缀,缓存有效时间,一般设置为1分钟

public class GoodsKey extends BasePrefix {
    public GoodsKey(int expireSeconds, String prefix) {

        super(expireSeconds, prefix);
    }

    public static GoodsKey getGoodsList = new GoodsKey(60, "gl");
    public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd");
}

2、URL缓存

当进行redis缓存时,在页面缓存基础上加入了路径上的参数(如goodsId),这里的url缓存和页面缓存相差不大,针对不同的详情页显示不同缓存页面,对不同的url进行缓存。

  @RequestMapping(value = "/to_detail2/{goodsId}",produces = "text/html")
  @ResponseBody
public String detail2(HttpServletRequest request, HttpServletResponse response,Model model, MiaoshaUser user, @PathVariable("goodsId") long goodsId) { model.addAttribute("user", user); //取缓存 String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class); if (!StringUtils.isEmpty(html)){ return html; } GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); model.addAttribute("goods", goods); long startAt = goods.getStartDate().getTime(); //转化为毫秒 long endAt = goods.getEndDate().getTime(); long now = System.currentTimeMillis(); int miaoshaStatus = 0; //秒杀状态 int remainSeconds = 0; //距离开始秒杀还有多久 if (now < startAt){ //秒杀还没开始,倒计时 miaoshaStatus = 0; remainSeconds = (int)((startAt - now)/1000); }else if (now > endAt){ //秒杀已结束 miaoshaStatus = 2; remainSeconds = -1; }else { //秒杀进行中 miaoshaStatus = 1; remainSeconds = 0; } model.addAttribute("miaoshaStatus", miaoshaStatus); model.addAttribute("remainSeconds", remainSeconds); // return "goods_detail"; //手动渲染 SpringWebContext ctx = new SpringWebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap(), applicationContext); html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", ctx); if (!StringUtils.isEmpty(html)){ redisService.set(GoodsKey.getGoodsDetail, ""+goodsId, html); } return html; }

3、对象缓存

更细粒度的缓存,对象缓存就是当用到用户数据的时候,可以从缓存中取出,将对象id作为key对象信息作为value存入reids缓存中,对象缓存一般没有有效期,永久有效。

先去取缓存,如果缓存中取不到,就去取数据库,然后再加载到缓存中去。

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

更新用户密码:更新数据库与缓存,保证数据一致性,先更新数据库,再删除缓存,防止数据库信息与缓存不一致,不能直接删除token,删除之后就不能登录了。

  public boolean updatePassword(String token, long id, String formPass){
        //取user
        MiaoshaUser miaoshaUser = getById(id);
        if (miaoshaUser == null){
            throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
        }
        //更新数据库
        MiaoshaUser toBeUpdate = new MiaoshaUser();
        toBeUpdate.setId(id);
        toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, miaoshaUser.getSalt()));
        miaoshaUserDao.update(toBeUpdate);
        //处理缓存
        redisService.delete(MiaoshaUserKey.getById, ""+id);
        miaoshaUser.setPassword(toBeUpdate.getPassword());
        redisService.set(MiaoshaUserKey.token, token, miaoshaUser);
        return true;
    }

4、页面静态化

前后端分离,把静态页面直接缓存到用户的浏览器端,当用户访问页面的时候就不需要跟我们的服务端进行交互了,就直接可以从本地缓存中拿到这个页面。application.properties里面添加配置静态资源配置项

#static
spring.resources.add-mappings=true
spring.resources.cache-period=3600
spring.resources.chain.cache=true
spring.resources.chain.enabled=true
spring.resources.chain.gzipped=true
spring.resources.chain.html-application-cache=true
spring.resources.static-locations=classpath:/static/

商品详情静态化

定义一个GoodsDetailVo存放传到前端的数据

public class GoodsDetailVo {
    private int miaoshaStatus = 0;
    private int remainSeconds = 0;
    private GoodsVo goodsVo;
    private MiaoshaUser miaoshaUser;

 修改detail接口

@RequestMapping(value = "/detail/{goodsId}")
    @ResponseBody
    public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user, @PathVariable("goodsId") long goodsId) {
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);

        long startAt = goods.getStartDate().getTime();  //转化为毫秒
        long endAt = goods.getEndDate().getTime();
        long now = System.currentTimeMillis();

        int miaoshaStatus = 0;  //秒杀状态
        int remainSeconds = 0;  //距离开始秒杀还有多久

        if (now < startAt){     //秒杀还没开始,倒计时
            miaoshaStatus = 0;
            remainSeconds = (int)((startAt - now)/1000);
        }else if (now > endAt){ //秒杀已结束
            miaoshaStatus = 2;
            remainSeconds = -1;
        }else {                 //秒杀进行中
            miaoshaStatus = 1;
            remainSeconds = 0;
        }
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        goodsDetailVo.setGoodsVo(goods);
        goodsDetailVo.setMiaoshaUser(user);
        goodsDetailVo.setMiaoshaStatus(miaoshaStatus);
        goodsDetailVo.setRemainSeconds(remainSeconds);
        return Result.success(goodsDetailVo);
    }

}

在static目录下新建goods_detail.htm页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>商品详情</title>

    <!-- 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>

</head>
<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>
                <!--<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">-->
                <!--<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>-->
                <!--<input type="hidden" name="goodsId" value="${goods.id}"/>-->
                <!--</form>-->
                <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>
</body>
<script>    //页面初始化就执行

function doMiaosha() {
    $.ajax({
        url:"miaosha/do_miaosha",
        type:"POST",
        data:{
            goodsId:$("#goodsId").val(),
        },
        success:function (data) {
            if (data.code == 0){
                window.location.href = "/order_detail.htm?orderId=" + data.data.id;
            } else{
                layer.msg(data.msg);
            }
        } ,
        error:function () {
            layer.msg("客户端请求有误");
        }
    });
}

function render(detail){
    var miaoshaStatus = detail.miaoshaStatus;
    var remainSeconds = detail.remainSeconds;
    var goods = detail.goodsVo;
    var user = detail.miaoshaUser;
    if (user){
        $("#userTip").hide();
    }
    $("#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();
    getDetail();
});

function getDetail() {
    var goodsId = g_getQueryString("goodsId");
    $.ajax({
        url:"/goods/detail/" + goodsId,
        type:"GET",
        success:function (data) {
            if (data.code == 0){
                render(data.data);
            } else{
                layer.msg(data.msg);
            }
        },
        error:function () {
            layer.msg("客户端请求有误");
        }
    })
}

function countDown() {
    // var remainSeconds = $("#countDown").text();     <!-- 这样写的话,在秒杀进行中和秒杀已结束时就没有值 -->
    var remainSeconds = $("#remainSeconds").val();  <!-- 在隐藏域中取 -->
    var timeout;
    if (remainSeconds > 0){ //秒杀还没开始,倒计时
        $("#buyButton").attr("disabled", true); //按钮不能点
        $("#miaoshaTip").html("秒杀倒计时:" + remainSeconds + "秒");
        timeout = setTimeout(function () {
            $("#countDown").text(remainSeconds - 1);    //input标签用的是value       文案随着改
            $("#remainSeconds").val(remainSeconds - 1); //span标签用的是text
            countDown();    //不断回调countDown方法
        }, 1000);   //过1秒之后,setTimeout就会执行
    }else if(remainSeconds == 0){   //秒杀进行中
        $("#buyButton").attr("disabled", false);
        if (timeout){
            clearTimeout(timeout);  //自带的清除函数吧
        }
        $("#miaoshaTip").html("秒杀进行中"); //等到remainSeconds减到0时,改文案
    }else { //秒杀已经结束
        $("#buyButton").attr("disabled", true);
        $("#miaoshaTip").html("秒杀已结束");
    }
}
</script>
</html>

秒杀静态化

修改do_miaosha接口

   @RequestMapping(value = "/do_miaosha",method = RequestMethod.POST)
   @ResponseBody
    public Result<OrderInfo> doMiaosha(Model model, MiaoshaUser user, @RequestParam("goodsId") long goodsId){
        model.addAttribute("user", user);
        if (user == null){
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //判断秒杀库存
        GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goodsVo.getStockCount();
        if (stock <= 0){
            model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
            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);
    }

goods_detail.htm

function doMiaosha() {
    $.ajax({
        url:"miaosha/do_miaosha",
        type:"POST",
        data:{
            goodsId:$("#goodsId").val(),
        },
        success:function (data) {
            if (data.code == 0){
                window.location.href = "/order_detail.htm?orderId=" + data.data.id;
            } else{
                layer.msg(data.msg);
            }
        } ,
        error:function () {
            layer.msg("客户端请求有误");
        }
    });
}

订单详情静态化

OrderController.java

import com.imooc.miaosha.domain.MiaoshaUser;
import com.imooc.miaosha.domain.OrderInfo;
import com.imooc.miaosha.result.CodeMsg;
import com.imooc.miaosha.result.Result;
import com.imooc.miaosha.service.GoodsService;
import com.imooc.miaosha.service.OrderService;
import com.imooc.miaosha.vo.GoodsVo;
import com.imooc.miaosha.vo.OrderDetailVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/order")
public class OrderController {

    @Autowired
    OrderService orderService;

    @Autowired
    GoodsService goodsService;

    @RequestMapping("/detail")
    @ResponseBody
    public Result<OrderDetailVo> detail(MiaoshaUser miaoshaUser, @RequestParam("orderId") long orderId){
        if (miaoshaUser == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        OrderInfo orderInfo = orderService.getOrderById(orderId);
        if (orderInfo == null) {
            return Result.error(CodeMsg.ORDER_NOT_EXIST);
        }
        long goodsId = orderInfo.getGoodsId();
        GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
        OrderDetailVo orderDetailVo = new OrderDetailVo();
        orderDetailVo.setOrderInfo(orderInfo);
        orderDetailVo.setGoodsVo(goodsVo);
        return Result.success(orderDetailVo);
    }
}

OrderDetailVo.java

public class OrderDetailVo {
    private GoodsVo goodsVo;
    private OrderInfo orderInfo;
}

order_detail.htm

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>订单详情</title>

    <!-- 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>

</head>
<body>
    <div class="panel panel-default">
        <div class="panel-heading">秒杀订单详情</div>
        <table class="table" id="goodslist">
            <tr>
                <td>商品名称</td>
                <td id="goodsName" colspan="3"></td>
            </tr>
            <tr>
                <td>商品图片</td>
                <td colspan="2">
                    <img id="goodsImg" width="200" height="200"/>
                </td>
            </tr>
            <tr>
                <td>订单价格</td>
                <td colspan="2" id="orderPrice"></td>
            </tr>
            <tr>
                <td>下单时间</td>
                <td id="createDate" colspan="2"></td>
            </tr>
            <tr>
                <td>订单状态</td>
                <td id="orderStatus"></td>
                <td>
                    <button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
                </td>
            </tr>
            <tr>
                <td>收货人</td>
                <td colspan="2">yanguobin 15842674359</td>
            </tr>
            <tr>
                <td>收获地址</td>
                <td colspan="2">中国 北京</td>
            </tr>
        </table>
    </div>
</body>
</html>
<script>
    function render(detail){
        var goods = detail.goodsVo;
        var order = detail.orderInfo;
        $("#goodsName").text(goods.goodsName);
        $("#goodsImg").attr("src", goods.goodsImg);
        $("#orderPrice").text(order.goodsPrice);
        $("#createDate").text(new Date(order.createDate).format("yyyy-MM-dd hh:mm:ss"));
        var status = "";
        if (order.status == 0){
            status = "未支付";
        } else if(order.status =1){
            status = "待发货";
        }
        $("#orderStatus").text(status);
    }

    $(function () {
        getOrderDetail();
    })

    function getOrderDetail() {
        var orderId = g_getQueryString("orderId");
        $.ajax({
            url:"/order/detail",
            type:"GET",
            data:{
                orderId:orderId,
            },
            success:function (data) {
                if (data.code == 0){
                    render(data.data);
                } else{
                    layer.msg(data.msg);
                }
            },
            error:function () {
                layer.msg("客户端请求有误");
            }
        })
    }
</script>

5、静态资源优化

1.JS/CSS压缩,减少流量

2.多个JS/CSS组合,减少连接数

3.CDN就近访问,CDN,内容分发网络。我们把静态的资源(html/css/js)放在CDN上,以加快用户获取数据的速度。

用户访问页面时,优先从最近的CDN服务器上获取页面资源,而不是从单一的网站服务器上获取。只有CDN获取不到才会访问后端服务器。

因此,我们可以使用CDN进行网站的加速优化,把静态资源(或某些动态资源)推送到CDN站点上。

 

 

 

posted @ 2020-06-11 21:32  llby  阅读(189)  评论(0)    收藏  举报