文章目录
优化思路
- 页面缓存+ URL缓存+对象缓存
- 页面静态化,前后端分离
- 静态资源优化
- CDN优化
(一)页面缓存+ URL缓存+对象缓存
redis缓存详情页
(1)页面缓存:商品列表
- 取缓存
- 手动渲染模版
- 结果输出
访问一个页面的时候不是直接系统渲染,而是从缓存里面取,缓存里面没有才去渲染(同时缓存到redis当中)
以goods_list为例:
因为要把goodsList存到redis当中,所以在redis包中new一个class:
/* GoodsController */
//原本是这样的
@RequestMapping("to_list")
public String list(Model model,MiaoshaUser user){
model.addAttribute("user",user);
List<GoodsVo> listGoodsVo = goodsService.listGoodsVo();
model.addAttribute("goodsList",listGoodsVo);
return "goods_list";
}
//变成这样
@Autowired
ThymeleadViewResolver thymeleadViewResolver;
@Autowired
ApplicationContext applicationContext;
@RequestMapping(value="to_list",produces="text/html")
@ResponseBody
public String list(Model model,MiaoshaUser user){
model.addAttribute("user",user);
//从缓存里面先取出来
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
//如果不为空就直接返回
if(!StringUtils.isEmpty(html)){
return html;
}
List<GoodsVo> listGoodsVo = goodsService.listGoodsVo();
model.addAttribute("goodsList",listGoodsVo);
//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;
}
手动渲染的时候,我们需要利用
thymeleafViewResolver.getTemplateEngine().process(String templates, webContext ctx)
这个方法
首先要生成WebContext
WebContext ctx = new WebContext( request, response, request.getServletContext(), request.getLocale(),model.asMap());
再传入到上面那个方法里面就可以了
注意点
-
页面有效期比较短,所以需要修改KeyPrefix中的expireSeconds
package com.pro.miaosha.redis; public class GoodsKey extends BasePrefix{ private GoodsKey(int expireSeconds,String prefix) { super(expireSeconds,prefix); } public static GoodsKey getGoodsList = new GoodsKey(60,"gl");//这里设置时间为60s }
-
验证:访问完http://localhost:8080/goods/to_list之后查看redis发现已经被缓存了
(2) URL缓存:商品详情
商品详情页面:
//原本是这样的
@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;
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";
}
//改成这样
@RequestMapping(value="/detail/{goodsId}")
@ResponseBody
public String detail(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,
@PathVariable("goodsId")long goodsId) {
//从缓存里面先取出来
String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class);
//如果不为空就直接返回
if(!StringUtils.isEmpty(html)){
return html;
}
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;
}
//否则需要手动渲染
WebContext ctx = new WebContext( request, response, request.getServletContext(), request.getLocale(),model.asMap());
html = thymeleafViewResolver.getTemplateEngine().process("goods_detail",ctx);
if(StringUtils.isEmpty(html)){
redisService.set(GoodsKey.getGoodsDetail,"",html);
}
return html;
}
(关于里面的这个SpringwebContext:)
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(html == null || html.length() <= 0) {
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
//手动渲染模板
html = renderTemplate("goods_list", model, thymeleafViewResolver,request,response,applicationContext);
if(html!=null && html.length() > 0) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
}
(3)对象缓存:用户token,getById改造
url和页面缓存时间比较短,适合变化不大的页面
以miaoshaUserService为例
改造一下getByid:
首先在miaoshaUserKey中加上一个新的getById
public MiaoshaUser getByid(long id ){
MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, "" + id, MiaoshaUser.class);
if (user != null) {
return user;
}
user = miaoshaUserDao.getById(id);
if (user != null) {
redisService.set(MiaoshaUserKey.getById, "" + id, user);
}
return user;
}
但是注意有update这个操作
updatePassword(String token,long id,String newPassword){
getById(id);
if(user == null){
throw new global exception();
}
//更新数据库成功了
userToBeUpdate = new miaoshaUser();
userToBeUpdate.set(id);
userToBeUpdatt.setPa(md5Util.formpass2dbpass,user.getSalt);
//修改缓存
redisService.delete(miaoshaUserKey.getById,""+id);
user.setPassword(userToBeUpdate)
redisService.set(miaoshaUserKey.token,token,user);//不删除,
}
还需要在redisService中添加一个delete方法
public boolean delete(KeyPrefix prefix, String key) {
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
String realKey = prefix.getPrefix()+key;
long ret = jedis.del(key);
return ret > 0;
}
finally{
returnToPool(jedis);
}
}
miaoshaUserDao中增加update方法
@Update("update miaosha_user set password = #{password} where id = #{id}")
public void update(MiaoshaUser toBeUpdate);
ps:Service 只能调用service不许调用别人的dao
压测商品列表,对比QPS
(二)页面静态化,把页面缓存到客户端
(1)详情页静态化改造
把页面缓存到浏览器上
静态化详情页:
//原来是这样的
@ResponseBody
@RequestMapping(value="/to_detail/{goodsId}",produces = "text/html")
public String detail(Model model, MiaoshaUser user, @PathVariable("goodsId") long goodsId,HttpServletRequest request,HttpServletResponse response) {
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";
// 否则需要手动渲染
WebContext ctx = new WebContext(request, response, request.getServletContext(), request.getLocale(),
model.asMap());
html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", ctx);
if (!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsDetail, "", html);
}
return html;
}
//现在
@ResponseBody
@RequestMapping(value="/to_detail/{goodsId}")
//String -> Result<GoodDetailVo>
public Result<GoodDetailVo> detail(Model model, MiaoshaUser user, @PathVariable("goodsId") long goodsId,HttpServletRequest request,HttpServletResponse response,MiaoshaUser 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;
if (now < startAt) {
miaoshaStatus = 0;
remainSeconds = (int) ((startAt - now) / 1000);
} else if (now > endAt) {
miaoshaStatus = 2;
remainSeconds = -1;
} else {
miaoshaStatus = 1;
remainSeconds = 0;
}
//增加这一部分
GoodDetailVo vo = new GoodDetailVo();
vo.setGoods(goods);
vo.setUser(user);
vo.setRemainSeconds(remainSeconds);
vo.setMiaoshaStatus(miaoshaStatus);
return Result.success(vo);
}
添加
public class GoodsDetailVo {
private int miaoshaStatus = 0;
private int remainSeconds = 0;
private MiaoshaUser user;
private GoodsVo goods;
//还有getter,setter
}
原本从商品列表跳转到缓存的时候:
现在要把静态页面放在static里面(顺便把后缀改成htm,如果不改还是会找之前templates里面的那个,虽然我个人觉得这个做法好像不太规范):
再改成这样:
goods_detail里面的thymeleaf删除掉
这里面th打头的全部删除掉:
还有@{也删掉:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oe24XgT4-1581767254415)(/Users/dylan/Library/Application Support/typora-user-images/image-20200212105155103.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7az7CoX-1581767254417)(/Users/dylan/Library/Application Support/typora-user-images/image-20200212105458345.png)]
开始写下面的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.goods;
var user = detail.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);
$("#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 = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0){//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀倒计时:"+remainSeconds+"秒");
timeout = setTimeout(function(){
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
},1000);
}else if(remainSeconds == 0){//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout){
clearTimeout(timeout);
}
$("#miaoshaTip").html("秒杀进行中");
}else{//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀已经结束");
}
}
(2)改造秒杀接口
修改配置:application.properties
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/
后端的改动:
/*-----------注释掉的是以前的方法----------------*/
@RequestMapping(value="/do_miaosha",method = RequestMethod.POST)
public Result<OrderInfo> list(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
model.addAttribute("user", user);
if(user == null) {
//return "login";
return Result.error(CodeMsg.Session_error);
}
//判断库存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if(stock <= 0) {
//model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
//return "miaosha_fail";
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.repeat);
//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";
return Result.success(orderInfo);
}
(3)修改减少库存的SQL
(三)静态资源优化
(1)压缩
(2)合并