- 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中并运行,然后开始压测,并查看生成的订单信息。
- 3. 静态资源优化
- 3.1 JS/CSS压缩,减少流量
- 3.2 多个JS/CSS组合,减少连接数
- 3.3 CDN就近访问
- 4. CDN优化
页面缓存就是将请求访问的页面放到redis里保存,这种缓存技术一般用于不会经常变动信息,并且访问次数较多的页面,这样就不用每次都动态加载
商品列表页 页面缓存: 1 取缓存 2 手动渲染 3 结果输出
- 修改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;
}
- 编写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}
不同的详情页 显示不同缓存页面+渲染 实质一样
对象缓存就是将对象放到缓存中
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的判断
- 先给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中并运行,然后开始压测,并查看生成的订单信息。



