秒杀项目学习第五章

主要内容

  1. 页面缓存+URL缓存+对象缓存
  2. 页面静态化,前后端分离
  3. 静态资源优化
  4. CDN优化

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

页面缓存

  1. 取缓存
  2. 手动渲染模板
  3. 结果输出直接返回html页面

以获取商品列表页面为例

1.原先controller
在这里插入图片描述2.现在controller

@RequestMapping(value="/to_list", produces="text/html")
	@ResponseBody
	public String list(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) {
		model.addAttribute("user", user);
		//取缓存见3
		String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
		if(!StringUtils.isEmpty(html)) {
			return html;
		}
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		model.addAttribute("goodsList", goodsList);
//    	 return "goods_list";
		WebContext ctx = new WebContext(request,
				response,
				request.getServletContext(),
				request.getLocale(),
				model.asMap());
		//手动渲染
		html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
		if(!StringUtils.isEmpty(html)) {
			redisService.set(GoodsKey.getGoodsList, "", html);
		}
		return html;
	}

2.GoodsKey
一般页面缓存时间都比较短,时间长的话页面及时性就不会很好
在这里插入图片描述3.Redis中存的数据
在这里插入图片描述4.浏览器中的显示
在这里插入图片描述

URL缓存

以商品详情页面为例

1.以前的controller

@RequestMapping("/to_detail/{goodsId}")
    public String detail(Model model,MiaoshaUser user,
    		@PathVariable("goodsId")long goodsId) {
    	model.addAttribute("user", user);
    	
    	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;
    	int endSeconds = 0;
    	if(now < startAt ) {//秒杀还没开始,倒计时
    		miaoshaStatus = 0;
    		remainSeconds = (int)((startAt - now )/1000);
    		endSeconds = (int)((endAt - now )/1000);
    	}else  if(now > endAt){//秒杀已经结束
    		miaoshaStatus = 2;
    		remainSeconds = -1;
    	}else {//秒杀进行中
    		miaoshaStatus = 1;
    		remainSeconds = 0;
			endSeconds = (int)((endAt - now )/1000);
    	}
    	model.addAttribute("miaoshaStatus", miaoshaStatus);
    	model.addAttribute("remainSeconds", remainSeconds);
    	model.addAttribute("endSeconds", endSeconds);

        return "goods_detail";
    }

2.现在的controller

@RequestMapping(path = "/to_detail/{goodsid}",produces = "text/html")
	@ResponseBody
	public String toDetail(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;
    	int endSeconds = 0;
    	if(now < startAt ) {//秒杀还没开始,倒计时
    		miaoshaStatus = 0;
    		remainSeconds = (int)((startAt - now )/1000);
    		endSeconds = (int)((endAt - now )/1000);
    	}else  if(now > endAt){//秒杀已经结束
    		miaoshaStatus = 2;
    		remainSeconds = -1;
    	}else {//秒杀进行中
    		miaoshaStatus = 1;
    		remainSeconds = 0;
			endSeconds = (int)((endAt - now )/1000);
    	}
    	model.addAttribute("miaoshaStatus", miaoshaStatus);
    	model.addAttribute("remainSeconds", remainSeconds);
    	model.addAttribute("endSeconds", endSeconds);
//手动渲染
		WebContext context = new WebContext(request,
				response,
				request.getServletContext(),
				request.getLocale(),
				model.asMap());
		html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", context);
		if(!StringUtils.isEmpty(html)){
			//System.out.println(html);
			redisService.set(GoodsKey.getGoodsDetail,""+goodsid,html);
		}
		return html;
      //  return "goods_detail";
    }

和以前controller的区别

1.取缓存
在这里插入图片描述
2.渲染模板直接返回html
在这里插入图片描述
3.Redis中数据
在这里插入图片描述

和页面缓存的区别

  1. 页面缓存不存在随着url的变化页面变化的情况
  2. URL缓存虽然模板一样但是渲染的数据不一样,存到redis中时的key是不同的

对象缓存

更细粒度的缓存以前已经用到了,在分布式Session中我们把token映射成User时就是对象级缓存
在这里插入图片描述其他的类似用到数据库查询根据某值(token)取某值(User)的也可以这么改造

比如:MiaoshaUserService.getById方法
1.设置一个key
一般对象缓存都是永久有效
在这里插入图片描述2.以前的getByid方法
在这里插入图片描述
2.修改后的getByid方法
在这里插入图片描述注意:当更新已经缓存的对象时,一般步骤如下

  1. 取出要更新的对象
  2. 更新数据库
  3. 更新所有与此对象有关的缓存

对象缓存也说明了为啥Aservice使用Bservice中的功能时为啥不能直接用Bdao因为Bservice中可能存在缓存操作,调Bservice可以提高效率

二、页面静态化,前后端分离

  1. 常用技术AngularJS、Vue.js
  2. 优点:利用浏览器的缓存

商品详情页面静态化

1.GoodsController.detail方法

//页面静态化
	@RequestMapping(path = "/to_detail/{goodsid}")
	@ResponseBody
	public Result<GoodsDetailVo> detail(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;
		int endSeconds = 0;
		if(now < startAt ) {//秒杀还没开始,倒计时
			miaoshaStatus = 0;
			remainSeconds = (int)((startAt - now )/1000);
			endSeconds = (int)((endAt - now )/1000);
		}else  if(now > endAt){//秒杀已经结束
			miaoshaStatus = 2;
			remainSeconds = -1;
		}else {//秒杀进行中
			miaoshaStatus = 1;
			remainSeconds = 0;
			endSeconds = (int)((endAt - now )/1000);
		}
		//把以前放到model里的对象封装进GoodsDetailVo,见2
		GoodsDetailVo vo = new GoodsDetailVo();
		vo.setGoods(goods);
		vo.setMiaoshaStatus(miaoshaStatus);
		vo.setRemainSeconds(remainSeconds);
		vo.setEndSeconds(endSeconds);
		vo.setUser(user);
		return Result.success(vo);
		//  return "goods_detail";
	}

2.GoodsDetailVo

/**
 * 向页面传数据
 */
public class GoodsDetailVo {
    private int miaoshaStatus = 0;
    private int remainSeconds = 0;
    private int endSeconds = 0;
    private GoodsVo goods;
    private MiaoshaUser user;

3.修改goods_detail页面

  • 把goods_detail.html页面改名为goods_detail.htm放到static目录下去掉Thymeleaf的所有东西

在这里插入图片描述

改名原因是application.properties中配置了如下参数
spring.thymeleaf.prefix=classpath:/templates/	
spring.thymeleaf.suffix=.html
  • 修改goods_list.html中的跳转地址,直接跳转到页面
    在这里插入图片描述
  • 修改good_detail.htm中的代码

在这里插入图片描述

  • js代码,ajax请求json数据后填入html中
/*这是jQuery的写法
    * 原本为:
    *
    *    $(document).ready(function(){
            // 开始写 jQuery 代码...
         });
         * 页面加载完之后才会执行
    * */
    $(function(){

        getDetail();
        //countDown();
    });

    function getDetail() {
        //获取参数值
        var goodsId = g_getQueryString("goodsId");
        $.ajax({
            url:"/goods/to_detail/"+goodsId,
            type:"GET",
            success:function(data){
                //如果Result中code==0那么就是成功的
                if(data.code==0){
                    //渲染页面的方法
                    render(data.data);
                }else{
                    layer.msg(data.msg);
                }
            },
            error:function (){
                layer.msg("客户端请求错误333");
            }
        });
    }
    function render(vo) {
        var miaoshaStatus = vo.miaoshaStatus;
        var remainSeconds = vo.remainSeconds;
        var endSeconds = vo.endSeconds;
        var goods = vo.goods;
        var user = vo.user;
        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);
        $("#endSeconds").val(endSeconds);
        $("#goodsId").val(goods.id);
        $("#goodsPrice").text(goods.goodsPrice);
        $("#miaoshaPrice").text(goods.miaoshaPrice);
        $("#stockCount").text(goods.stockCount);
        //处理id=miaoshaTip
        countDown();
    }
    function countDown(){
        var remainSeconds = $("#remainSeconds").val();
        var endSeconds = $("#endSeconds").val();
        var timeout;
        var endtimeout;
        if(remainSeconds > 0){//秒杀还没开始,倒计时
            $("#buyButton").attr("disabled", true);
            timeout = setTimeout(function(){
                $("#miaoshaTip").html("剩余时间:"+(remainSeconds - 1));
                $("#remainSeconds").val(remainSeconds - 1);
                $("#endSeconds").val(endSeconds - 1);
                countDown();
            },1000);
        }else if(remainSeconds == 0){//秒杀进行中
            $("#buyButton").attr("disabled", false);
            if(timeout){
                clearTimeout(timeout);
            }
            if(endSeconds>0){
                $("#miaoshaTip").html("秒杀进行中");
            }else{
                $("#remainSeconds").val(remainSeconds - 1);
            }
            endtimeout = setTimeout(function(){
                $("#endSeconds").val(endSeconds - 1);
                countDown();
            },1000);
        }else{//秒杀已经结束
            if(endtimeout){
                clearTimeout(endtimeout);
            }
            $("#buyButton").attr("disabled", true);
            $("#miaoshaTip").html("秒杀已经结束");
        }
    }
    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("客户端请求有误")
            }
        });
    }

</script>

订单详情页静态化

看上边js代码的 doMiaosha方法,我们需要修改url:"/miaosha/do_miaosha"的方法

1.原本MiaoshaController.miaosha方法

@RequestMapping(path = "/do_miaosha",method = RequestMethod.POST)
    public String miaosha(Model model, MiaoshaUser user,
					   @RequestParam("goodsId")long goodsId) {
    	model.addAttribute("user", user);
    	if(user == null) {
    		return "/login";
    	}
    	//判断库存
    	GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
    	int stock = goods.getStockCount();
    	if(stock <= 0) {
    		model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
    		return "miaosha_fail";
    	}
    	//判断是否已经秒杀到了,不能重复秒杀
    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    	if(order != null) {
    		model.addAttribute("errmsg", CodeMsg.REPEATE_MIAOSHA.getMsg());
    		return "miaosha_fail";
    	}
    	//减库存 下订单 写入秒杀订单
    	OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
    	model.addAttribute("orderInfo", orderInfo);
    	model.addAttribute("goods", goods);
        return "order_detail";
    }

2.修改后的MiaoshaController.miaosha方法

也是返回json数据

@RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
    @ResponseBody
    public Result<OrderInfo> miaosha(Model model,MiaoshaUser user,
    		@RequestParam("goodsId")long goodsId) {
    	model.addAttribute("user", user);
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	//判断库存
    	GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10个商品,req1 req2
    	int stock = goods.getStockCount();
    	if(stock <= 0) {
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//判断是否已经秒杀到了
    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    	if(order != null) {
    		return Result.error(CodeMsg.REPEATE_MIAOSHA);
    	}
    	//减库存 下订单 写入秒杀订单
    	OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
        return Result.success(orderInfo);
    }

3.然后根据上边提到的js中的方法判断是否跳转到订单详情页
在这里插入图片描述4.同样的方法修改order_detail.html页面为order_detail.htm并放在static目录下
在这里插入图片描述
5.修改order_detail.htm的内容
取出Thymeleaf模板,使用ajax请求填充的数据。这里只帖出来ajax请求内容

<script>
    $(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("客户端请求错误");
            }
        });
        //渲染页面
        function render(detail){
            var goods = detail.goods;
            var order = detail.order;
            $("#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);

        }
    }
</script>

6.新增OrderController处理请求订单数据

@RequestMapping(path = "/detail",method = RequestMethod.GET)
    @ResponseBody
    public Result<OrderDetailVo> info(Model model, MiaoshaUser user,
									  @RequestParam("orderId") long orderId) {
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	OrderInfo order = orderService.getOrderById(orderId);
    	if(order == null) {
    		return Result.error(CodeMsg.ORDER_NOT_EXIST);
    	}
    	long goodsId = order.getGoodsId();
    	GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
    	OrderDetailVo vo = new OrderDetailVo();
    	vo.setOrder(order);
    	vo.setGoods(goods);
    	return Result.success(vo);
    }

7.OrderDetailVo 、orderService.getOrderById、orderDao.getOrderById

public class OrderDetailVo {
	private GoodsVo goods;
	private OrderInfo order;

在这里插入图片描述

在这里插入图片描述

添加Spring对静态文件的配置

#static
spring.resources.add-mappings=true
#对Resolver加缓存
spring.resources.chain.cache=true
spring.resources.chain.enabled=true
spring.resources.chain.html-application-cache=true
#指明静态文件路径
spring.resources.static-locations=classpath:/static/

使用一波
在这里插入图片描述

自我总结

  • 页面静态化无非就是使用纯html页面+ajax请求json数据后再填充页面
  • 若A页面跳转到B页面之前需要条件判断可以先在A页面中利用ajax请求判断后再跳转
  • 如果不需要条件判断可以直接跳转到B的静态页面,让B自己用ajax请求数据

题外话GET和POST的区别

  • get是幂等的,无论请求多少次得到的结果都一样
  • post,用于对服务端数据更改

三、超卖问题的解决

库存减为负数

出现问题的原因
在这里插入图片描述解决问题:
修改最后的更新sql,数据库更新时会加锁,让数据库保证数据正确性
在这里插入图片描述

一个用户秒杀到两件商品

出现问题的原因
开始是一个用户同时发出两个请求,通过controller中方法的如下检验

  • 判断库存
  • 判断是否已经秒杀到了

调用如下方法,造成一个用户秒杀到了两件商品
在这里插入图片描述

解决方法:
利用数据库的唯一索引特性,我们可以给miaosha_order表添加一个索引
在这里插入图片描述在这里插入图片描述
这样在创建订单的时候就会发现没法插入第二个订单造成回滚,防止一个用户同时秒杀两个。但是其实我们一般不会让一个用户同时发出两个请求,比如秒杀前先填个验证码之类的可以请求错开时间。

对于超卖问题一般都是想法从数据库层面解决问题

为了压测提高性能把OrderService.getMiaoshaOrderByUserIdGoodsId修改为对象缓存

1.以前的getMiaoshaOrderByUserIdGoodsId方法
在这里插入图片描述
2.修改一下直接从缓存中取
在这里插入图片描述
OrderKey

在这里插入图片描述3.为了能直接从缓存里取生成订单的时候得写入缓存
OrderService.createOrder
在这里插入图片描述

四、静态资源优化

  1. JS/CSS压缩,减少流量,如xx.min.js
  2. 多个JS/CSS组合,减少连接数,可以参考tengine
  3. CDN就近访问,多个节点缓存数据,找到离客户端最近的节点

总结

  1. 从浏览器开始使用页面静态化技术(前后端分离)缓存页面
  2. 浏览器发出请求时经过CDN处理
  3. 请求到达服务器时可以使用页面缓存、URL缓存、对象缓存
  4. 总之想尽办法减少对数据库的访问,但是也要考虑数据不一致的问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值