轉自:https://blog.csdn.net/qq_41305266/article/details/81140724javascript
咱們發現,目前系統最大的瓶頸就在數據庫訪問。所以,系統優化的方案核心在於減小數據庫的訪問,而緩存就是一個好方法。css
1、頁面緩存html
以商品列表爲例,Controller方法改造以下java
@RequestMapping(value = "/to_list", produces = "text/html")
@ResponseBody
public String toList(HttpServletRequest request, HttpServletResponse response, Model model,
SeckillUser seckillUser) {
// 取緩存
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if (!StringUtils.isEmpty(html)) {
return html;
}
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
// 手動渲染
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;
}
2、URL緩存jquery
跟頁面緩存原理同樣,只是根據不一樣的url參數從緩存中獲取不一樣的頁面數據web
以查看商品詳情的方法爲例,Controller方法改造以下ajax
@RequestMapping(value = "/to_detail/{goodsId}", produces = "text/html")
@ResponseBody
public String detail(HttpServletRequest request, HttpServletResponse response, Model model, SeckillUser seckillUser,
@PathVariable("goodsId") long goodsId) {
// 取緩存
String html = redisService.get(GoodsKey.getGoodsDetail, "" + goodsId, String.class);
if (!StringUtils.isEmpty(html)) {
return html;
}
model.addAttribute("user", seckillUser);
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods", goods);
long startAt = goods.getStartDate().getTime();
long endAt = goods.getEndDate().getTime();
long now = System.currentTimeMillis();
int seckillStatus = 0;
int remainSeconds = 0;
if (now < startAt) {// 秒殺還沒開始,倒計時
seckillStatus = 0;
remainSeconds = (int) ((startAt - now) / 1000);
} else if (now > endAt) {// 秒殺已經結束
seckillStatus = 2;
remainSeconds = -1;
} else {// 秒殺進行中
seckillStatus = 1;
remainSeconds = 0;
}
model.addAttribute("seckillStatus", seckillStatus);
model.addAttribute("remainSeconds", remainSeconds);
// 手動渲染
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、對象緩存redis
對象緩存控制粒度比頁面緩存細,但要注意對象變動時緩存值的處理spring
SeckillUserService方法修改以下:數據庫
public SeckillUser getById(long id){
//取緩存
SeckillUser user = redisService.get(SeckillUserKey.getById, "" + id, SeckillUser.class);
if(user != null){
return user;
}
//取數據庫
user = seckillUserDao.getById(id);
if(user != null){
redisService.set(SeckillUserKey.getById, "" + id, user);
}
return user;
}
public boolean updatePassword(long id, String token, String formPass){
SeckillUser user = getById(id);
if(user == null){
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
SeckillUser toBeUpdated = new SeckillUser();
toBeUpdated.setId(id);
toBeUpdated.setPassword(Md5Util.formPass2DbPass(formPass, user.getSalt()));
seckillUserDao.update(toBeUpdated);
redisService.delete(SeckillUserKey.getById, "" + id);
user.setPassword(toBeUpdated.getPassword());
redisService.set(SeckillUserKey.token, token, user);
return true;
}
4、頁面靜態化(先後端分離)
簡單的說,就是html頁面 + ajax。經過把靜態html頁面緩存到客戶端瀏覽器,經過ajax動態獲取數據,從而減小服務端訪問。
如今比較流行的方案是使用AngularJs或者VueJs。本節爲了演示方便直接使用html+jquery模擬。
新增vo用於傳輸數據
package com.wings.seckill.vo;
import com.wings.seckill.domain.SeckillUser;
public class GoodsDetailVo {
private int seckillStatus = 0;
private int remainSeconds = 0;
private GoodsVo goods;
private SeckillUser user;
public int getSeckillStatus() {
return seckillStatus;
}
public void setSeckillStatus(int seckillStatus) {
this.seckillStatus = seckillStatus;
}
public int getRemainSeconds() {
return remainSeconds;
}
public void setRemainSeconds(int remainSeconds) {
this.remainSeconds = remainSeconds;
}
public GoodsVo getGoods() {
return goods;
}
public void setGoods(GoodsVo goods) {
this.goods = goods;
}
public SeckillUser getUser() {
return user;
}
public void setUser(SeckillUser user) {
this.user = user;
}
}
獲取商品詳情的Controller方法以前是負責服務端動態獲取數據並渲染頁面,如今改成只負責返回動態數據,頁面緩存到客戶端瀏覽器。
@RequestMapping(value = "/detail/{goodsId}")
@ResponseBody
public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model,
SeckillUser 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 seckillStatus = 0;
int remainSeconds = 0;
if (now < startAt) {// 秒殺還沒開始,倒計時
seckillStatus = 0;
remainSeconds = (int) ((startAt - now) / 1000);
} else if (now > endAt) {// 秒殺已經結束
seckillStatus = 2;
remainSeconds = -1;
} else {// 秒殺進行中
seckillStatus = 1;
remainSeconds = 0;
}
GoodsDetailVo vo = new GoodsDetailVo();
vo.setGoods(goods);
vo.setUser(user);
vo.setRemainSeconds(remainSeconds);
vo.setSeckillStatus(seckillStatus);
return Result.success(vo);
}
商品列表中跳轉方式更改以下:
<td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">詳情</a></td>
新增商品詳情靜態頁面goods_detail.htm(注意是.htm,不是.html!)
<!DOCTYPE HTML>
<html >
<head>
<title>商品詳情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 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="seckillTip"></span>
</td>
<td>
<!--
<form id="seckillForm" method="post" action="/seckill/do_seckill">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">當即秒殺</button>
<input type="hidden" name="goodsId" id="goodsId" />
</form>-->
<button class="btn btn-primary btn-block" type="button" id="buyButton"onclick="doSeckill()">當即秒殺</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="seckillPrice"></td>
</tr>
<tr>
<td>庫存數量</td>
<td colspan="3" id="stockCount"></td>
</tr>
</table>
</div>
</body>
<script>
function doSeckill(){
$.ajax({
url:"/seckill/do_seckill",
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 seckillstatus = detail.seckillStatus;
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);
$("#seckillPrice").text(goods.seckillPrice);
$("#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);
$("#seckillTip").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);
}
$("#seckillTip").html("秒殺進行中");
}else{//秒殺已經結束
$("#buyButton").attr("disabled", true);
$("#seckillTip").html("秒殺已經結束");
}
}
</script>
</html>
common.js新增一下方法:
// 獲取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;
};
能夠看到,此刻的頁面是從緩存中獲取的!
然而,出現304還不夠,由於這意味着客戶端仍是要發一次請求給瀏覽器以確認靜態頁面是否有變化。
咱們但願客戶端直接根據緩存時間判斷是否須要從新請求靜態頁面,在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/
BTW:在谷歌瀏覽器上未能看到此現象。。。。。。
秒殺及訂單詳情接口靜態化
package com.wings.seckill.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wings.seckill.domain.OrderInfo;
import com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.result.CodeMsg;
import com.wings.seckill.result.Result;
import com.wings.seckill.service.GoodsService;
import com.wings.seckill.service.OrderService;
import com.wings.seckill.service.SeckillService;
import com.wings.seckill.service.SeckillUserService;
import com.wings.seckill.vo.GoodsVo;
@Controller
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
SeckillUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
SeckillService seckillService;
@RequestMapping(value = "/do_seckill", method = RequestMethod.POST)
@ResponseBody
public Result<OrderInfo> list(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId) {
if (user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
// 判斷庫存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if (stock <= 0) {
return Result.error(CodeMsg.SECKill_OVER);
}
// 判斷是否已經秒殺到了
SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);
if (order != null) {
return Result.error(CodeMsg.REPEATE_SECKILL);
}
// 減庫存 下訂單 寫入秒殺訂單
OrderInfo orderInfo = seckillService.seckill(user, goods);
return Result.success(orderInfo);
}
}
package com.wings.seckill.service;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.wings.seckill.dao.OrderDao;
import com.wings.seckill.domain.OrderInfo;
import com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.redis.OrderKey;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.vo.GoodsVo;
@Service
public class OrderService {
@Autowired
OrderDao orderDao;
@Autowired
RedisService redisService;
public SeckillOrder getSeckillOrderByUserIdGoodsId(long userId, long goodsId) {
SeckillOrder seckillOrder = redisService.get(OrderKey.getSeckillOrderByUidGid, "" + userId + "_" + goodsId,
SeckillOrder.class);
if (seckillOrder != null) {
return seckillOrder;
}
return orderDao.getSeckillOrderByUserIdGoodsId(userId, goodsId);
}
@Transactional
public OrderInfo createOrder(SeckillUser 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.getSeckillPrice());
orderInfo.setOrderChannel(1);
orderInfo.setStatus(0);
orderInfo.setUserId(user.getId());
long orderId = orderDao.insert(orderInfo);
SeckillOrder seckillOrder = new SeckillOrder();
seckillOrder.setGoodsId(goods.getId());
seckillOrder.setOrderId(orderId);
seckillOrder.setUserId(user.getId());
orderDao.insertSeckillOrder(seckillOrder);
redisService.set(OrderKey.getSeckillOrderByUidGid, "" + user.getId() + "_" + goods.getId(), seckillOrder);
return orderInfo;
}
public OrderInfo getOrderById(long orderId) {
return orderDao.getOrderById(orderId);
}
}
order_detail.htm
<!DOCTYPE HTML>
<html>
<head>
<title>訂單詳情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 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 colspan="3" id="goodsName"></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">XXX 18812341234</td>
</tr>
<tr>
<td>收貨地址</td>
<td colspan="2">北京市昌平區回龍觀龍博一區</td>
</tr>
</table>
</div>
</body>
</html>
<script>
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);
}
$(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.SQL語句限制
GoodsDao 減庫存方法更改以下:
、
@Update("update seckill_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")
public int reduceStock(SeckillGoods g);
2.創建惟一索引
6、靜態資源優化
1.JS/CSS壓縮,減小流量
2.多個JS/CSS組合,減小鏈接數
3.CDN就近訪問