頁面
-
登錄頁面
- 登錄成功后,跳轉(zhuǎn)商品列表
-
商品列表頁
- 加載商品信息
-
商品詳情頁
- 根據(jù)商品id查出商品信息
- 返回VO(包括rmiaoshaStatus、emainSeconds)
- 前端根據(jù)數(shù)據(jù)展示秒殺按鈕,點(diǎn)擊開始秒殺
-
訂單詳情頁
秒殺頁面設(shè)置
后端返回秒殺狀態(tài)miaoshaStatus,前端根據(jù)秒殺狀態(tài),設(shè)置頁面:
- 狀態(tài)碼 0, 未開始,倒計(jì)時(shí)
- 狀態(tài)碼 1, 已開始,顯示秒殺按鈕
- 狀態(tài)碼 2 ,已結(jié)束
剩余時(shí)間?remainSeconds
頁面加載時(shí),獲取remainSeconds 的值
- 未開始,remainSeconds = 開始時(shí)間-當(dāng)前時(shí)間
- 禁用秒殺按鈕,顯示倒計(jì)時(shí)
- 設(shè)置定時(shí)器,回調(diào)函數(shù),一秒一次,修改remainSeconds 值
- 直到 remainSeconds = 0
- 清除設(shè)置定時(shí)器,則修改頁面,啟用秒殺按鈕
- 已開始,remainSeconds = -1,啟用秒殺按鈕
- 已結(jié)束 ,remainSeconds = 0,禁用秒殺按鈕
倒計(jì)時(shí)功能
<span th:if="${user eq null}"> 您還沒有登錄,請登陸后再操作<br/></span>
<input type="hidden" id="remainSeconds" th:value="${remainSeconds}" />
<span th:if="${miaoshaStatus eq 0}">秒殺倒計(jì)時(shí):<span id="countDown" th:text="${remainSeconds}"></span>秒</span>
<span th:if="${miaoshaStatus eq 1}">秒殺進(jìn)行中</span>
<span th:if="${miaoshaStatus eq 2}">秒殺已結(jié)束</span>
秒殺業(yè)務(wù)邏輯
點(diǎn)擊秒殺按鈕,傳遞商品ID, 秒殺商品,form表單提交到后端
-
判斷庫存
-
是否重復(fù)秒殺
- 查詢訂單信息。如存在,則表示已經(jīng)秒殺過了
-
減庫存、下訂單、寫入秒殺訂單(事務(wù))
- 傳入?yún)?shù)(user,goods)用戶秒殺商品
- 秒殺成功后,生成訂單信息,包含兩個(gè)
- 訂單詳細(xì)信息
- 秒殺訂單信息,包括user_id、order_id、goods_id,便于設(shè)置唯一索引(user_id、goods_id)
-
支付模塊
頁面優(yōu)化
- 頁面緩存+URL緩存(Thymeleaf)
- 對象緩存
- 頁面靜態(tài)化,前后端分離
- 靜態(tài)資源優(yōu)化
- CDN優(yōu)化
緩存
頁面緩存
- 從緩存中取html源代碼,非空返回(緩存命中)
- 若緩存為空(緩存失效)
-
手動渲染
- thymeleafViewResolver.getTemplateEngine,模板引擎
- WebContext,包含業(yè)務(wù)數(shù)據(jù)
- 同時(shí)添加頁面緩存,頁面緩存有效期(比如60秒)
- 返回html源代碼
-
手動渲染
- 頁面緩存,一般有效期比較短,保證數(shù)據(jù)及時(shí)性
@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(); // return "goods_list"; model.addAttribute("goodsList", goodsList); // 業(yè)務(wù)數(shù)據(jù) 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); } // 返回html return html; } }
URL緩存
-
和頁面緩存類似,例如
- 頁面緩存商品列表,無參數(shù)
- URL緩存商品詳情,接口有ID參數(shù),ID不同商品詳情不同
-
參數(shù)不同,頁面信息不同
-
頁面緩存、url緩存有效期比較短,適用于頁面變化不大的場景(如商品列表,商品列表有分頁,只緩存前幾頁)
對象級緩存
-
更細(xì)顆粒度的緩存,比如:
-
登錄成功時(shí),用戶信息寫入緩存
- 服務(wù)端通過token獲取
-
秒殺成功生成訂單時(shí),訂單信息寫入緩存
- 查詢是否已經(jīng)秒殺過時(shí),查緩存,不查數(shù)據(jù)庫,減少負(fù)載
-
-
數(shù)據(jù)更新時(shí),也要處理緩存
- 先更新數(shù)據(jù)庫
- 再讓緩存失效(刪除、更新)
Cache Aside Pattern
- 失效:應(yīng)用程序先從cache取數(shù)據(jù),沒有得到,則從數(shù)據(jù)庫中取數(shù)據(jù),成功后,放到緩存中。
- 命中:應(yīng)用程序從cache中取數(shù)據(jù),取到后返回。
- 更新:先把數(shù)據(jù)存到數(shù)據(jù)庫中,成功后,再讓緩存失效。
- 試想,兩個(gè)并發(fā)操作,一個(gè)是更新操作,另一個(gè)是查詢操作
- 更新操作刪除緩存后,查詢操作沒有命中緩存,先把老數(shù)據(jù)讀出來后放到緩存中,然后更新操作更新了數(shù)據(jù)庫。
- 于是,在緩存中的數(shù)據(jù)還是老的數(shù)據(jù),導(dǎo)致緩存中的數(shù)據(jù)是臟的。
前后端分離
- 常用技術(shù):AngularJS、Vue.js、React
- 優(yōu)點(diǎn):利用瀏覽器的緩存
前后端分離
- 靜態(tài)數(shù)據(jù)緩存,動態(tài)數(shù)據(jù)調(diào)接口
- 頁面靜態(tài)化,html、css、js、image緩存到瀏覽器端
- 動態(tài)數(shù)據(jù)通過服務(wù)端獲取
- 不需要使用頁面緩存和URL緩存了,瀏覽器端已經(jīng)進(jìn)行了緩存,再用頁面緩存和URL緩存沒有什么意義
- 對象緩存可以繼續(xù)使用
- 前后端分離后,不經(jīng)過服務(wù)端,客戶端直接跳轉(zhuǎn)到商品詳情頁面,然后商品詳情頁面Ajax請求動態(tài)數(shù)據(jù)
商品詳情靜態(tài)化、訂單詳情靜態(tài)化
-
使用jquery模擬,不使用thymeleaf
-
商品詳情使用原生html,頁面跳轉(zhuǎn)時(shí),直接跳轉(zhuǎn)到html頁面
-
然后Ajax請求動態(tài)數(shù)據(jù),jquery填充頁面
-
同時(shí)根據(jù)miaoshaStatus、remainSeconds,修改頁面
秒殺接口前后端分離
后端僅返回給前端所需的數(shù)據(jù),不再渲染 HTML 頁面,不再控制前端的效果。
前端得到數(shù)據(jù),自己渲染。
前端和后端只關(guān)注于自己的邏輯判斷和控制。后端僅需提供給前端 API 即可。
- 不需要表單提交
- 直接按鈕?onclick?,ajax請求數(shù)據(jù)
- post請求
靜態(tài)文件配置
- 配置靜態(tài)文件路徑
- 不配置,304(前后端仍然交互了一次)
- 配置后,200(靜態(tài)文件直接從瀏覽器取緩存,不需要訪問服務(wù)器,減少交互)
- 配置后,減少前后端交互
- Cache-Control:max-age=3600,緩存時(shí)間
靜態(tài)資源優(yōu)化
-
JS/CSS壓縮,減少流量
-
多個(gè)JS/CSS組合,減少連接數(shù)
-
CDN就近訪問
tengine
- 組合多個(gè)CSS、JS文件的訪問請求變成一個(gè)清求
- 自動去除空白字符和注釋從而減小頁面的體積(webpack工具,打包)
CDN
全稱是Content Delivery Network,**內(nèi)容分發(fā)網(wǎng)絡(luò)。**根據(jù)用戶位置等,訪問最近的鏡像
- 可以將網(wǎng)站的靜態(tài)資源(如圖片、CSS、JavaScript 等文件)緩存到全球各地的服務(wù)器上
- 當(dāng)用戶請求這些資源時(shí),可以從離用戶最近的服務(wù)器上獲取資源,從而提高資源的訪問速度和用戶的訪問體驗(yàn)。
CDN 的主要作用是:
- 提高網(wǎng)站的訪問速度
- 降低帶寬成本
- 提高可用性
同樣,在高并發(fā)場景下,CDN 可以發(fā)揮重要的作用。
由于高并發(fā)場景下會有大量的用戶同時(shí)訪問網(wǎng)站,如果所有的請求都直接訪問源站,就會導(dǎo)致源站的帶寬和服務(wù)器資源受到過大的壓力,從而導(dǎo)致網(wǎng)站的訪問速度變慢或者出現(xiàn)宕機(jī)等問題。
因此,在高并發(fā)場景下,使用 CDN 可以將流量分散到全球各地的服務(wù)器上,從而減輕源站的壓力,提高網(wǎng)站的訪問速度和可用性。
接口優(yōu)化
并發(fā)大問題,瓶頸在于數(shù)據(jù)庫,思路:減少數(shù)據(jù)庫訪問。解決:
-
各種緩存,減少數(shù)據(jù)庫負(fù)載
-
接口優(yōu)化:減少數(shù)據(jù)庫訪問
-
數(shù)據(jù)庫分庫分表(mycat中間件)
方案
- Redis預(yù)減庫存減少數(shù)據(jù)庫訪問
- 內(nèi)存標(biāo)記減少Redis訪問
- 請求先入隊(duì)緩沖,異步下單,增強(qiáng)用戶體驗(yàn)
- Nginx水平擴(kuò)展
思路
- 系統(tǒng)初始化,把商品庫存數(shù)量加載到Redis
- 收到請求,Redis預(yù)減庫存,庫存不足,直接返回,否則進(jìn)入3
- 請求入隊(duì),立即返回排隊(duì)中
- 請求出隊(duì),生成訂單,減少庫存
- 客戶端輪詢,是否秒殺成功
加載庫存
- 實(shí)現(xiàn)?InitializingBean接口,覆寫afterPropertiesSet()方法
- 系統(tǒng)初始化調(diào)用此方法,加載庫存、內(nèi)存標(biāo)記到緩存
內(nèi)存標(biāo)記
- 減少redis負(fù)載
- 訪問秒殺接口時(shí),先訪問redis內(nèi)存標(biāo)記,若為true,則直接返回,商品賣完啦
- 設(shè)置redis內(nèi)存標(biāo)記
- 初始化為?false,放到緩存中
- redis庫存小于0時(shí),設(shè)置為true
入隊(duì)緩沖
- 庫存足夠,沒有秒殺過,進(jìn)入下個(gè)步驟,入隊(duì)
- 用戶,商品id(用戶秒殺商品)作為消息,發(fā)送消息到隊(duì)列
- 消息接受者監(jiān)聽接收消息,異步下單
- 判斷數(shù)據(jù)庫庫存(是否庫存大于0)
- 判斷是否已經(jīng)秒殺到了(是否存在秒殺訂單)
- 減庫存(成功才下單)
- 下訂單
- 寫入秒殺訂單(唯一索引)
輪詢
-
秒殺成功后,返回排隊(duì)中,前端開始輪詢(類比12306買票,不馬上返回是否搶票成功,顯示正在排隊(duì)中)
-
先判斷是否存在訂單 order,存在,表示秒殺成功,返回 orderId
-
若order==null,判斷緩存標(biāo)記
-
緩存標(biāo)記 == false,表示沒有賣完,繼續(xù)輪詢,返回 0
-
緩存標(biāo)記 == true,表示賣完啦,返回 -1
-
orderId:成功
-
0: 排隊(duì)中
-
-1:秒殺失敗
- 設(shè)置一個(gè)緩存標(biāo)記(false)
- 減庫存時(shí),若庫存為0時(shí),設(shè)置緩存表示已經(jīng)秒殺完畢,修改緩存標(biāo)記(true)
- 輪詢時(shí),若緩存標(biāo)記為true,則賣完啦,返回秒殺失敗 -1
-
RabbitMQ
-
同步直接調(diào)用轉(zhuǎn)換成異步間接推送,把瞬時(shí)并發(fā)的大量請求平推出去,削弱峰值
-
秒殺請求過來時(shí),先入隊(duì)緩沖瞬時(shí)流量,直接返回客戶端正在排隊(duì)中
-
然后出隊(duì),生成訂單,修改庫存
-
同時(shí)客戶端定時(shí)輪詢,定時(shí)查詢是否秒殺成功
-
出隊(duì)和輪詢同時(shí)進(jìn)行
配置
- 創(chuàng)建隊(duì)列
- 創(chuàng)建交換機(jī)
- 隊(duì)列和交換機(jī)綁定(routingKey)
創(chuàng)建消息發(fā)送者
- 發(fā)送消息到隊(duì)列或交換機(jī)
創(chuàng)建消息接受者
-
@RabbitListener(queues=隊(duì)列名字/交換機(jī)名字)
-
監(jiān)聽隊(duì)列或交換機(jī),接受消息
消息傳播:消息->交換機(jī)->隊(duì)列
Exchange
交換機(jī)和隊(duì)列綁定(Key、無key、key-value)
發(fā)送消息到交換機(jī),匹配成功,隊(duì)列才會收到消息
四種交換機(jī)模式:
- Direct Exchange:按照routingkey分發(fā)到指定隊(duì)列
- Topic Exchange,多關(guān)鍵字匹配,發(fā)給多個(gè)隊(duì)列,交換機(jī)和隊(duì)列通過routingkey綁定
- 發(fā)送消息到交換機(jī)時(shí),交換機(jī)key和隊(duì)列key若匹配,則消息發(fā)送到隊(duì)列
- Fanout Exchange,廣播模式,無routingkey的概念,和交換機(jī)綁定的隊(duì)列都能獲得消息
- Headers Exchange ,交換機(jī)和隊(duì)列通過key-value綁定
- 發(fā)送消息時(shí),key-value匹配,隊(duì)列全部滿足或者滿足任何一個(gè),隊(duì)列才會收到消息
安全優(yōu)化
方案
- 秒殺接口地址隱藏(防止明文暴露,提前搶票、黃牛機(jī)器人搶票)
- 數(shù)學(xué)公式驗(yàn)證碼(保護(hù)獲取秒殺地址的接口,分散請求)
- 接口限流防刷(保護(hù),防止惡意刷接口)
前端頁面限制,防君子不防小人,主要防止用戶出錯(cuò)
Http明文,url可以提前拿到,防止惡意刷接口
思路
點(diǎn)擊秒殺之前,先輸入驗(yàn)證碼,分散用戶的請求
- 添加生成驗(yàn)證碼的接口
- 在獲取秒殺路徑的時(shí)候,驗(yàn)證驗(yàn)證碼
- ScriptEngine使用
秒殺接口地址隱藏
思路:秒殺開始之前,先去請求接口獲取秒殺地址
- 秒殺接口改造,帶上PathVariable參數(shù)
- 添加生成地址的接口
- 秒殺收到請求,先驗(yàn)證PathVariable
獲取秒殺地址的接口也可能暴露,通過驗(yàn)證碼驗(yàn)證
邏輯:請求生成地址接口、返回path、秒殺接口拼接path(真正接口)、訪問真正秒殺接口、先驗(yàn)證path
隱藏秒殺地址可以有很多種實(shí)現(xiàn)方式,上述只是一種實(shí)現(xiàn)方式,還可以:
- 接口可以返回302,跳轉(zhuǎn)到新的頁面,新的頁面才是真正的秒殺頁面
- 接口可以返回一個(gè)頁面的url,讓瀏覽器跳轉(zhuǎn)到這個(gè)新的url頁面,新的頁面才是真正的秒殺頁面
- 活動開始前和活動開始后是兩個(gè)完全不同的頁面,這樣就可以防止提前抓取網(wǎng)頁了
- 為了防止惡意用戶提前抓取網(wǎng)頁,對網(wǎng)頁進(jìn)行分析,然后寫出刷接口的機(jī)器人工具。
- 活動開始之前,惡意用戶就算分析網(wǎng)頁寫了刷接口的程序也沒用,因?yàn)槟莻€(gè)頁面并不是真正的秒殺頁面。
數(shù)學(xué)公式驗(yàn)證碼
思路:點(diǎn)擊秒殺之前,先輸入驗(yàn)證碼,分散用戶的請求,防止機(jī)器人
- 添加生成驗(yàn)證碼的接口(答案寫入緩存)
- 在點(diǎn)擊秒殺獲取路徑的時(shí)候,去驗(yàn)證驗(yàn)證碼(防止暴露獲取動態(tài)路徑 path 的地址)
- ScriptEngine使用
邏輯:
- 頁面生成驗(yàn)證碼,同時(shí)驗(yàn)證碼答案寫入緩存(key+uerId+goodsId,有效時(shí)間)
- 用戶輸入驗(yàn)證碼
- 點(diǎn)擊按鈕,訪問生成地址接口(獲取秒殺路徑)
- 驗(yàn)證,用戶輸入和緩存驗(yàn)證
- 驗(yàn)證成功后刪掉緩存(防止再次使用)
- 然后生成path(驗(yàn)證成功后,才生成path),返回
- 訪問真正秒殺接口
使用驗(yàn)證碼目的
- 防止機(jī)器人,刷票軟件;
- 延緩請求,錯(cuò)峰請求秒殺接口;
- 保護(hù)作用,驗(yàn)證碼不對,直接返回錯(cuò)誤代碼。
- 防止 獲取秒殺地址接口 暴露
接口限流防刷
攔截器和緩存實(shí)現(xiàn),設(shè)置緩存,有效期內(nèi)限制訪問次數(shù);過期失效,重新限制
- 首先自定義方法注解,放到秒殺接口的方法上
- 接著注冊攔截器并覆寫方法
- 服務(wù)器接到請求時(shí),攔截器會先攔截請求,并獲得注解
- 然后在覆寫的方法中進(jìn)行邏輯判斷
邏輯:
- 查詢訪問次數(shù)(查詢緩存)
- 為null,設(shè)置緩存
- 不為null
- 小于等于限制次數(shù),+1
- 大于限制次數(shù),返回失敗
攔截器寫通用方法
-
自定義注解,接口加上注解
-
// 5秒鐘,最大訪問次數(shù)限制5次,需要登錄 @AccessLimit(seconds=5, maxCount=5, needLogin=true)
-
@Retention(RUNTIME) @Target(METHOD) public @interface AccessLimit { int seconds(); int maxCount(); boolean needLogin() default true; }
-
自定義攔截器,獲取注解信息,進(jìn)行限制
-
攔截器在參數(shù)解析器前執(zhí)行,都需要在WebMvcConfigurer中注冊
-
ThreadLocal,存放數(shù)據(jù)到當(dāng)前線程,每個(gè)線程單獨(dú)一份
常見的限流算法
- 在網(wǎng)關(guān)上做限流。比如在nginx上寫lua腳本來實(shí)現(xiàn)
- 在應(yīng)用上做單機(jī)限流。使用諸如基于Guava的RateLimiter令牌桶的方式
- 在應(yīng)用上做分布式限流。比如redisson提供了個(gè)基于redis的RateLimiter
- 如果是SpringCloud項(xiàng)目,可用的就更多了,比如SpringCloud?Gateway,Sentinel等等。
服務(wù)端優(yōu)化
Tomcat
內(nèi)存優(yōu)化
- -Xms2048M 最小內(nèi)存,-Xmx2048M 最大內(nèi)存都設(shè)為 2G;
- -XX:+HeapDumpOnOutOfMemoryError;
- -XX:HeapDumpPath=$CATALINA_HOME/logs/heap.dump";
- 內(nèi)存溢出時(shí),將內(nèi)存映像放到$CATALINA_HOME/logs/heap.dump 文件,方便定位問題。
并發(fā)優(yōu)化
- maxConnections:服務(wù)器支持最大并發(fā)連接數(shù)量
- acceptCount:當(dāng)服務(wù)器的并發(fā)連接數(shù)都在使用時(shí),會有一個(gè)隊(duì)列來存放新來的請求,隊(duì)列滿時(shí)接收到的任何請求都將被拒絕
- maxThreads:最大工作線程數(shù)
- minSpareThreads:最小空閑的工作線程
- autoDeploy: 指 Tomcat 在運(yùn)行時(shí)是否應(yīng)該定期檢查新的或更新的 web 應(yīng)用程序,禁用提高性能。
- reloadable:監(jiān)視 WEB-INF/classes/ and /WEB-INF/lib 中的類,并在檢測到更改時(shí)自動重新加載 web 應(yīng)用程序,可以設(shè)置為 true/false。
APR 優(yōu)化
Tomcat 的 BIO、NIO、APR 模式,默認(rèn) NIO 模式
- APR:Apache HTTP 服務(wù)器的支持庫。可以簡單地理解為,Tomcat 將以 JNI 的形式調(diào)用 Apache HTTP 服務(wù)器的核心動態(tài)鏈接庫來處理文件讀取或網(wǎng)絡(luò)傳輸操作,從而大大地提高 Tomcat 對靜態(tài)文件的處理性能。Tomcat APR 也是在 Tomcat 上運(yùn)行高并發(fā)應(yīng)用的首選模式。
nginx
反向代理文章來源:http://www.zghlxwxcb.cn/news/detail-815642.html
- 反向代理到集群(server_pool_miaosha;),配置多臺服務(wù)器(localhost、otherserver等)
負(fù)載均衡文章來源地址http://www.zghlxwxcb.cn/news/detail-815642.html
- 服務(wù)器按照權(quán)重分配請求數(shù)量,如上圖,weight=1,均分請求
- max_fails=2,最大失敗次數(shù),判斷服務(wù)器是否存活,超過次數(shù),認(rèn)為服務(wù)器掛掉,不會分配請求了
到了這里,關(guān)于實(shí)現(xiàn)秒殺功能設(shè)計(jì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!