學習秒殺系統-頁面優化技術

文章目錄

  • 前言
  • 頁面緩存+URL緩存+對象緩存
    • 頁面緩存
      • 取緩存
      • 手動渲染
    • URL緩存
    • 對象緩存
  • 頁面靜態化,前后端分離(常用)
    • GET POST區別
    • 如何解決超賣?重復賣?(簡單版)
  • 靜態資源優化
    • 多個JS/CSS組合,減少連接數
    • CDN優化
  • 總結

前言

本篇重點學習頁面緩存,頁面靜態化,剩下的了解即可。

頁面緩存+URL緩存+對象緩存

業務的瓶頸就是數據庫,而這些頁面(大粒度),對象(小粒度)都需要查詢數據庫 所以最大力度加緩存。

頁面緩存

什么是頁面緩存?我們訪問一個頁面的時候,不是讓服務端渲染而是從緩存里邊去,如果找到返回給客戶端,如果沒有再讓服務端渲染并返回給客戶端,同時保存到redis中。
與之前沒有頁面緩存相比,省時省力在哪?
首先,之前的方法是我們每次請求一個頁面,服務端都需要從數據庫中查詢相關數據并渲染到頁面中然后返回給客戶端
頁面緩存如何改進的? 直接從緩存中獲取頁面的信息并直接返回給客戶端。 省去查詢數據庫和渲染頁面。什么是渲染頁面?在服務端渲染頁面時從接受請求開始,查詢數據庫,然后加載JS/CSS文件再將動態數據添加到頁面中,最后返回一個完整的html頁面的過程

以商品列表為例
如何實現?1取緩存 2緩存未命中的話手動渲染頁面并返回

取緩存

//如果緩存中有頁面且TTL沒有過期
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);if(!StringUtils.isEmpty(html)) {return html;}

手動渲染

		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 );//手動渲染,goods_list是具體html文件,要不然thymeleafViewResolver也不知道頁面長啥樣啊html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);//存儲到緩存中if(!StringUtils.isEmpty(html)) {redisService.set(GoodsKey.getGoodsList, "", html);}return html;

這里需要注意的是,我們將頁面緩存到redis時都會設置有效期,過了有效期我們就需要重新從數據庫中加載

URL緩存

URL緩存本質上就是頁面緩存的一種,只不過需要在地址欄中傳參數。區別就是get和set方法都要有路徑的參數

    @RequestMapping(value="/to_detail2/{goodsId}",produces="text/html")@ResponseBodypublic 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;}

對象緩存

像我們之前緩存user一樣,每次從緩存中取出對應token的user。但我們這里的有效期是動態的,也就是用戶在有效期沒過期之前登錄了就會自動延長有效期(具體就是從緩存里邊查詢出來不為空,就延長)。和之前的靜態有效期有區別。

頁面靜態化,前后端分離(常用)

為什么有這個呢?
你如果只做了頁面緩存,會發現渲染頁面其實本質上還是服務端做
但你做了頁面靜態化,瀏覽器會將html頁面緩存在客戶端,這樣頁面數據渲染時客戶端做,遇到動態的數據再去請求服務端。

前端:對于動態數據我們需要獲取,添加以下方法


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("秒殺已經結束");}
}

后端:需要取出相關數據并返回,這里注意方法要加@ResponseBody 因為是返回數據,不是返回頁面了。

    @RequestMapping(value="/detail/{goodsId}")@ResponseBodypublic 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 vo = new GoodsDetailVo();vo.setGoods(goods);vo.setUser(user);vo.setRemainSeconds(remainSeconds);vo.setMiaoshaStatus(miaoshaStatus);返回數據return Result.success(vo);}

這樣是不是感覺比頁面緩存還慢呀,因為每次訪問時確實是瀏覽器解析頁面,但動態數據都需要去加載動態數據,而加載動態數據就需要查詢數據庫。而頁面緩存直接可以從緩存中取。其實這里還會有個配置,當沒過期時,瀏覽器直接加載緩存中的靜態頁面。無需請求服務端,當過期時,則需要重新訪問服務器重新加載動態數據。若在過期時間內,我們更新了數據,客戶端不會自動收到提示,我們需要主動提示客戶端我們更新數據了(這部分具體實現,之后會說,大家先了解這些原理)。

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/

當我們點擊秒殺時前端:

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("客戶端請求有誤");}});}

需要將商品id傳遞過去 ,這里為什么是post(重點)

GET POST區別

這道題面試會經常問,網上答案很多,最重要的區別是以下兩點(不要再說查詢數據和請求數據了,這倆本質上都能查詢和請求)
GET 冪等 無論你get多少次,服務端數據不會變化,且狀態不會改變
POST 顯然不冪等 你提交數據(新增,添加,修改,刪除),服務端數據可能會變化,會改變狀態。
改變狀態怎么理解?比如登錄方法 客戶端請求方式為POST ,服務端數據不會發生改變,但是登錄完了之后會創建session啊,生成token等等一系列操作。

為什么 <a href="/delete?id=xxx">是很嚴重的錯誤?
1 首先a標簽是超鏈接 它是get方法,服務端數據不應該發生變化。
2 其次用戶很容偽造一些get方法來刪除數據,因為get方法主要通過路徑參數來刪除數據。

改為post就安全了嘛? 是的 post方法通過請求體傳參,而請求體里邊可以包含token,我們更容易通過token先校驗然后再取數據操作。

如何解決超賣?重復賣?(簡單版)

解決超賣
本節課解決超賣的方式很簡單,就是將更新數據庫時的SQL語句加個判斷條件,如果庫存大于0則執行更新。為什么在這里加可以,因為數據庫系統 會采用MVCC機制(主頁里講過),修改操作(增,刪,改)sql語句會加鎖,所以多線程并發訪問時,一個表只會有一個線程修改,所以我們可以直接在SQL語句中加上判斷條件。
解決重復賣?
什么是重復賣?一個用戶對于秒殺商品購買了兩次
如何解決 為相應的字段的添加唯一索引。比如秒殺商品表中有用戶id和商品id字段我們為這兩個字段加上唯一索引。這樣用戶id和商品id的字段值在此表中只能有一個,重復秒殺會報錯。
唯一索引并不是指“這張表中只有一個索引”,而是指“這個索引要求字段值唯一”。一張表可以有多個索引,包括多個普通索引和多個唯一索引。

普通索引:允許表中的字段值重復。例如,在 users 表的 username 字段上創建普通索引,可以快速查找 username,但同一個 username 可以出現多次。
唯一索引:要求表中的字段值必須唯一。如果嘗試插入或更新違反唯一性約束的數據,數據庫會拋出錯誤。

靜態資源優化

這些在我們代碼中體現不出來,所以自行search吧 。面試中也不是特別重要

多個JS/CSS組合,減少連接數

CDN優化

總結

并發大的瓶頸 是在于數據庫
如何解決?
加緩存,首先用戶發起請求,瀏覽器通過頁面靜態化,可以直接把頁面緩存到瀏覽器,請求到達服務端之前請求會首先訪問CDN節點。通過CDN,繼續到服務端之前 還可以加ngix緩存,ngix沒有就是服務端的頁面緩存,然后在細粒度是對象緩存,最后才是數據庫。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90156.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90156.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90156.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

QCC系列顯示交互層的自研技術突破與實踐

在音頻設備智能化進程中&#xff0c;顯示交互的流暢度與兼容性已成為用戶體驗的核心指標。傳統方案中&#xff0c;TFT 彩屏與多語言適配常面臨硬件驅動沖突、功耗失控、字符顯示錯亂等問題。作為高通平臺十年級方案商&#xff0c;騰泰技術在 QCC 系列中聚焦顯示交互層的自研技術…

JMeter 實現 Protobuf 加密解密

一、 .proto文件編譯成.jar文件 相關依賴下載詳見&#xff1a;將 message.proto 編譯成 .jar文件 1.依賴于java編譯環境 2.依賴protoc編譯jar包 編譯目錄 1.創建一個根目錄&#xff1a;protobuf 2.在protobuf下創建build、output、lib、src目錄 lib&#xff1a;放 protobu…

發票識別在費控系統應用剖析

一、發票識別與費控系統的融合價值1.1 解決傳統費控痛點效率瓶頸突破&#xff1a;將人工處理每張發票的5-8分鐘縮短至秒級自動識別準確性飛躍&#xff1a;關鍵字段識別準確率從人工的95%提升至99%以上合規性強化&#xff1a;自動對接稅務系統驗真&#xff0c;虛假發票識別率提升…

Rust實戰:決策樹與隨機森林實現

基于 Rust 實現決策樹(Decision Tree)和隨機森林(Random Forest)的實例 Linfa的基本定義 Linfa是意大利語中“淋巴”(lymph)的意思,在醫學領域指淋巴系統相關的結構或功能。淋巴系統由淋巴管、淋巴結、脾臟等組成,負責免疫防御和體液平衡。 Linfa在生物學中的作用 …

9. isaacsim4.2教程-ROS加相機/CLOCK

在本示例中&#xff0c;我們將學習如何&#xff1a; 向場景中添加額外的相機并將其安裝在機器人上 添加相機發布器&#xff08;Camera Publishers&#xff09; 通過 rostopics 發送真實的合成感知數據&#xff08;ground truth synthetic perception data&#xff09; 前提…

微信小程序171~180

1.封裝購物車接口API import http from /utils/httpexport const reqAddCrt ({ goodsId, count, ...data }) > {return http.get(/cart/addToCart/${goodsId}/${count}, data) }export const reqCartList () > {return http.get(/cart/getCartList) }export const reqU…

修改 docker 容器的掛載配置(保持數據不丟的情況)

一、核心原理Docker 容器的運行時配置&#xff08;包括掛載&#xff09;是啟動時確定的&#xff0c;一旦啟動無法直接修改。因此&#xff0c;需通過以下步驟實現&#xff1a;保存原容器中的數據&#xff08;避免丟失&#xff09;&#xff1b;基于原鏡像創建新容器&#xff0c;同…

MVCC(多版本并發控制)介紹及實現原理

一、什么是MVCC&#xff1f; MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并發控制&#xff09;是數據庫中用于解決并發訪問問題的一種機制。它通過為數據維護多個版本&#xff0c;讓讀寫操作在不同版本上獨立進行&#xff0c;從而避免了傳統鎖機制中…

密碼學基礎概念詳解:從古典加密到現代密碼體系

一、引言&#xff1a;為什么我們需要密碼學&#xff1f; 在數字化時代&#xff0c;信息已成為核心生產要素&#xff0c;而信息安全則是保障社會運轉的基石。當我們在電商平臺輸入銀行卡密碼時&#xff0c;當我們通過即時通訊工具發送私密消息時&#xff0c;當企業在云端存儲核心…

小鵬汽車視覺算法面試30問全景精解

小鵬汽車視覺算法面試30問全景精解 ——智能駕駛 車路協同 視覺創新:小鵬汽車視覺算法面試核心考點全覽 前言 小鵬汽車作為中國智能電動汽車的創新引領者,致力于通過AI與自動駕駛技術推動智能出行的變革。小鵬視覺算法團隊深耕自動駕駛感知、車路協同、智能座艙、3D重建…

程序是如何生成的-以c語言為例

一&#xff0c;序言 從代碼到能跑的程序&#xff0c;整個過程就像 “把外文翻譯成母語&#xff0c;再組裝成能直接用的東西”&#xff0c;一步步來更清楚&#xff1a; 源代碼&#xff08;程序員寫的代碼&#xff0c;如C語言文件&#xff09;↓ 預處理&#xff08;處理#開頭的命…

風險識別清單:構建動態化的風險管理體系

在項目管理實踐中&#xff0c;風險識別是確保項目成功的關鍵環節。PMBOK提出的風險提示清單&#xff08;Prompt List&#xff09;為項目團隊提供了一個系統化的思考框架&#xff0c;幫助突破個人經驗局限&#xff0c;實現更全面的風險覆蓋。這一工具的價值不僅在于其提供的標準…

從“點狀用例”到“質量生態”:現代軟件測試的演進、困局與破局

測試的三次范式躍遷業務高速迭代下的四大困局質量工程化&#xff1a;流程、平臺、度量三位一體左移與右移&#xff1a;把缺陷扼殺在搖籃&#xff0c;也把監控鋪到墳墓自動化金字塔的再平衡&#xff1a;UI、API、單元、契約、e2e數據驅動測試&#xff1a;從“拍腦袋”到“科學實…

【C++】繼承和多態擴展學習

目錄 1. 菱形虛擬繼承原理剖析 1.1.虛基表 2. 單繼承和多繼承的虛函數表深入探索 2.1 單繼承虛函數表深入探索 2.2 多繼承虛函數表深入探索 ?編輯 2.3 菱形繼承、菱形虛擬繼承 3. 繼承和多態考察的一些常見問題 1. 菱形虛擬繼承原理剖析 繼承的文章中我們講到C的多繼承…

Visual Studio Code 遠端云服務器開發使用指南

目錄 一、下載安裝 1、官方下載 2、下載加速方案 二、基于Ubuntu系統的開發環境搭建方案 1、開發環境配置 2、云服務器架構 3、工作流程關系 4、總結 三、推薦插件 1、免配置插件 1. Remote-SSH - 遠程登錄Linux服務器 2. C/C - 必備的C/C開發插件 3. C/C Extensi…

技術演進中的開發沉思-41 MFC系列:定制 AppWizard

MFC開發&#xff0c;最為重要的無非就是用“MFC AppWizard” 對話框做開發了&#xff0c;第一次使用感覺像拆收音機的孩子 —— 左邊是項目類型選擇&#xff0c;右邊是一堆打勾的選項&#xff0c;點完 “完成”&#xff0c;屏幕上就冒出了能直接編譯運行的窗口程序。那時還不知…

Libevent(3)之使用教程(2)創建事件

Libevent(3)之使用教程(2)創建事件 Author: Once Day Date: 2025年6月29日 一位熱衷于Linux學習和開發的菜鳥&#xff0c;試圖譜寫一場冒險之旅&#xff0c;也許終點只是一場白日夢… 漫漫長路&#xff0c;有人對你微笑過嘛… 本文檔翻譯于&#xff1a;Fast portable non-bl…

Kotlin 作用域函數 let 的實現原理

Kotlin 中的 let 是一個 標準庫擴展函數&#xff0c;它廣泛用于作用域函數&#xff08;Scope Functions&#xff09;中&#xff0c;尤其適用于對可空對象&#xff08;nullable&#xff09;做非空判斷并執行代碼塊的場景。 示例代碼 val name: String? "123" name?…

從FDTD仿真到光學神經網絡:機器學習在光子器件設計中的前沿應用工坊

FDTD仿真與光學神經網絡的基礎概念 FDTD&#xff08;時域有限差分&#xff09;是一種數值方法&#xff0c;用于求解麥克斯韋方程組&#xff0c;廣泛應用于光子器件設計。光學神經網絡通過光波導、衍射元件等物理結構實現矩陣運算&#xff0c;具有低能耗、高并行的優勢。 機器學…

在Ubutu22系統上面離線安裝Go語言環境【教程】

0.引言 Go語言&#xff08;又稱Golang&#xff09;是Google開發的一種靜態強類型、編譯型、并發型編程語言&#xff0c;由Robert Griesemer、Rob Pike和Ken Thompson于2007年開始設計&#xff0c;2009年正式發布。 1.到官網下載壓縮包 2.從win10系統離線上傳壓縮包給ubuntu22…