秒杀优化
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站点上。
浙公网安备 33010602011771号