栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

秒杀项目05-页面优化技术

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

秒杀项目05-页面优化技术

秒杀项目05-页面优化技术
  • 1. 页面缓存+URL缓存+对象缓存
    • 1.1 页面缓存
    • 1.2 URL缓存
    • 1.3 对象缓存(更细粒度的缓存)
  • 2. 页面静态化,前后端分离
    • 1. 常用技术AngularJS、Vue.js
    • 2. 优点: 利用浏览器的缓存
    • 3. 商品详情页面静态化(将页面与动态的内容分离)
      • 修改Controller层的方法
      • 添加Vo对象
      • 修改common.js添加时间格式化函数和获取url参数的函数
      • 将商品详情页面放到/resources/static目录下
      • 修改goods_list页面的部分内容
    • 4. 订单详情页面静态化
      • 新增CodeMsg
      • 修改Controller层
      • 添加vo对象
      • 将订单详情页面放到/resources/static目录下
      • 修改商品详情页的部分内容
      • 添加静态资源配置
      • 浏览器缓存
  • 解决一个bug
  • 一些问题
    • 解决超卖问题
    • 一个人秒杀多次的问题
    • 当进入秒杀业务的人数>库存数时,要判断当前用户是否减库存成功,成功则生成订单,失败抛出异常
  • 优化
    • 将miaoshaoOrder信息作为对象缓存缓存到redis中
      • 添加OrderKey
      • 修改OrderService
  • 重新压测
    • 先启动MyUtil类的主方法,让5000个用户的token被保存在redis中.
    • 将jar包放到centos中并运行,然后开始压测,并查看生成的订单信息。![在这里插入图片描述](https://img-blog.csdnimg.cn/f34870862629448397edaa890e7968d6.png)
  • 3. 静态资源优化
    • 3.1 JS/CSS压缩,减少流量
    • 3.2 多个JS/CSS组合,减少连接数
    • 3.3 CDN就近访问
  • 4. CDN优化

1. 页面缓存+URL缓存+对象缓存 1.1 页面缓存

页面缓存就是将请求访问的页面放到redis里保存,这种缓存技术一般用于不会经常变动信息,并且访问次数较多的页面,这样就不用每次都动态加载
商品列表页 页面缓存: 1 取缓存 2 手动渲染 3 结果输出

  1. 修改GoodsController中的/goods/to_list/请求返回的内容,使其返回html页面的内容。
	@RequestMapping(value = "/to_list", produces = "text/html")
    @ResponseBody
    public String toList(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) {

        //取缓存
        String html = redisService.get(GoodsKey.getGoodsList,"", String.class);
        if (!StringUtils.isEmpty(html)) {
            return html;
        }
        //查询商品列表
        List 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.isNotBlank(html)) {
            redisService.set(GoodsKey.getGoodsList, "", html);
        }
        return html;
    }
  1. 编写GoodsKey(存储redis中的key和expireTime)
public class GoodsKey extends basePrefix{

    private GoodsKey(int expireSeconds, String prefix) {
        super(expireSeconds, prefix);
    }

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

}
1.2 URL缓存

这里的URL缓存相当于页面缓存,只是针对详情页/goods/to_detail/{goodsId}
不同的详情页 显示不同缓存页面+渲染 实质一样

1.3 对象缓存(更细粒度的缓存)

对象缓存就是将对象放到缓存中
MiaoshaUserservice.java

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

    public boolean updatePassword(String token, long id, String formPass) {
        //取user
        MiaoshaUser user = getById(id);
        if (user == null) {
            throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
        }
        //更新数据库
        MiaoshaUser toBeUpdate = new MiaoshaUser();
        toBeUpdate.setId(id);
        toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
        miaoshaUserMapper.update(toBeUpdate);
        //处理缓存,防止数据库中与redis缓存中的数据不一致,所有涉及到该对象的缓存都要处理
        redisService.delete(MiaoshaUserKey.getById, "" + id);
        user.setPassword(toBeUpdate.getPassword());
        redisService.set(MiaoshaUserKey.token, token, user);
        return true;
    }
2. 页面静态化,前后端分离 1. 常用技术AngularJS、Vue.js 2. 优点: 利用浏览器的缓存 3. 商品详情页面静态化(将页面与动态的内容分离) 修改Controller层的方法

GoodsController.java

	@RequestMapping(value = "/detail/{goodsId}")
    @ResponseBody
    public Result detail(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 vo = new GoodsDetailVo();
        vo.setGoods(goods);
        vo.setUser(user);
        vo.setRemainSeconds(remainSeconds);
        vo.setMiaoshaStatus(miaoshaStatus);
        return Result.success(vo);
    }
添加Vo对象

GoodsDetailVo.java

@Data
public class GoodsDetailVo {

    private int miaoshaStatus = 0;
    private int remainSeconds = 0;
    private GoodsVo goods;
    private MiaoshaUser user;
}
修改common.js添加时间格式化函数和获取url参数的函数
//展示loading
function g_showLoading(){
	var idx = layer.msg('处理中...', {icon: 16,shade: [0.5, '#f5f5f5'],scrollbar: false,offset: '0px', time:100000}) ;  
	return idx;
}
//salt
var g_passsword_salt="hmxP@ssw0rd"
// 获取url参数
function g_getQueryString(name) {
	var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
	var r = window.location.search.substr(1).match(reg);
	if(r != null) return unescape(r[2]);
	return null;
};
//设定时间格式化函数,使用new Date().format("yyyyMMddhhmmss");
Date.prototype.format = function (format) {
	var args = {
		"M+": this.getMonth() + 1,
		"d+": this.getDate(),
		"h+": this.getHours(),
		"m+": this.getMinutes(),
		"s+": this.getSeconds(),
	};
	if (/(y+)/.test(format))
		format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	for (var i in args) {
		var n = args[i];
		if (new RegExp("(" + i + ")").test(format))
			format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? n : ("00" + n).substr(("" + n).length));
	}
	return format;
};

将商品详情页面放到/resources/static目录下


并修改其内容,使其不再依赖Thymeleaf模板引擎
goods_detail.html




    商品详情
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    



秒杀商品详情
您还没有登录,请登陆后再操作
没有收货地址的提示。。。
商品名称
商品图片
秒杀开始时间
商品原价
秒杀价
库存数量
修改goods_list页面的部分内容

4. 订单详情页面静态化 新增CodeMsg
	//订单模块 5004XX
    ORDER_NOT_EXIST(500400, "订单不存在"),
修改Controller层

MiaoshaController.java

@RequestMapping("/miaosha")
@Controller
public class MiaoshaController {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private MiaoshaService miaoshaService;

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

}

添加vo对象

OrderDetailVo.java

@Data
public class OrderDetailVo {
    private GoodsVo goods;
    private OrderInfo order;
}
将订单详情页面放到/resources/static目录下


order_detail.html




    订单详情
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    


秒杀订单详情
商品名称
商品图片
订单价格
下单时间
订单状态
收货人 XXX 18812341234
收货地址 北京市昌平区回龙观龙博一区
修改商品详情页的部分内容


good_detail.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.html?orderId="+data.data.id;
            } else {
                layer.msg(data.msg);
            }
        },
        error:function() {
            layer.msg("客户端请求有误");
        }
    })
}
添加静态资源配置
# static
# SPRING RESOURCES HANDLING (ResourceProperties)
# 是否启用默认资源处理
spring.web.resources.add-mappings=true
# 资源处理程序服务的资源的缓存周期。如果未指定持续时间后缀,则将使用秒。可以被 'spring.web.resources.cache.cachecontrol' 属性覆盖。
spring.web.resources.cache.period=3600
# 是否启用资源链中的缓存。
spring.web.resources.chain.cache=true
# 是否启用 Spring 资源处理链。默认情况下,除非至少启用了一种策略,否则禁用。
spring.web.resources.chain.enabled=true
# 是否启用已压缩资源的解析(gzip、brotli)。检查具有“.gz”或“.br”文件扩展名的资源名称。
spring.web.resources.chain.compressed=false
# 静态资源的位置。默认为类路径:classpath:/static/
spring.web.resources.static-locations=classpath:/static/

浏览器缓存

解决一个bug

修改OrderService中的创建订单方法

一些问题 解决超卖问题

当高并发访问秒杀请求时,会出现超卖问题,我们可以通过在数据库减库存操作时,在SQL语句上加上stockCount>0的判断

一个人秒杀多次的问题
  1. 先给miaosha_order表的userid和goodsid添加唯一索引
当进入秒杀业务的人数>库存数时,要判断当前用户是否减库存成功,成功则生成订单,失败抛出异常

MiaoshaService.java

@Transactional
    public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
        // 减库存 下订单 写入秒杀订单
        int row = goodsService.reduceStock(goods);
        if (row > 0) {
            // order_info miaosha_order
            return orderService.createOrder(user, goods);
        }
        //row == 0 说明减库存失败,订单也不应该创建,报错回滚
        throw new GlobalException(CodeMsg.MIAO_SHA_OVER);
    }
优化 将miaoshaoOrder信息作为对象缓存缓存到redis中 添加OrderKey

OrderKey.java

public class OrderKey extends basePrefix {

	public OrderKey(String prefix) {
		super(prefix);
	}
	public static OrderKey getMiaoshaOrderByUidGid = new OrderKey("moug");
}
修改OrderService

OrderService.java

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RedisService redisService;

    public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {
        //return orderMapper.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
        return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class);
    }

	@Transactional
    public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCreateDate(new Date());
        orderInfo.setDeliveryAddrId(0L);
        orderInfo.setGoodsCount(1);
        orderInfo.setGoodsId(goods.getId());
        orderInfo.setGoodsName(goods.getGoodsName());
        orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
        orderInfo.setOrderChannel(1);
        orderInfo.setStatus(1);
        orderInfo.setStatus(0);
        orderInfo.setUserId(user.getId());
        //insert方法会把自增的主键设置到对象属性中
        orderMapper.insert(orderInfo);
        MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
        miaoshaOrder.setGoodsId(goods.getId());
        miaoshaOrder.setOrderId(orderInfo.getId());
        miaoshaOrder.setUserId(user.getId());
        orderMapper.insertMiaoshaOrder(miaoshaOrder);
        redisService.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goods.getId(), miaoshaOrder);
        return orderInfo;
    }

    public OrderInfo getOrderById(long orderId) {
        return orderMapper.selectById(orderId);
    }
}

重新压测 先启动MyUtil类的主方法,让5000个用户的token被保存在redis中. 将jar包放到centos中并运行,然后开始压测,并查看生成的订单信息。


3. 静态资源优化 3.1 JS/CSS压缩,减少流量 3.2 多个JS/CSS组合,减少连接数 3.3 CDN就近访问 4. CDN优化
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/337366.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号