秒杀项目总结及面试常见问题
前言:
- 這篇文章是慕課網(wǎng)秒殺項(xiàng)目的總結(jié),主講人是Joshua,B站上搜Java秒殺就可以獲取資源,為了防止鏈接過(guò)期,這里就不貼鏈接了,大家自己可以去B站搜
- 秒殺項(xiàng)目的思路大致是一樣的,只是有些功能更齊全,核心思想都是解決高并發(fā),所以可能項(xiàng)目不同但是同樣有借鑒作用
- 本文主要對(duì)邏輯進(jìn)行梳理,不包含代碼
- 文末有總結(jié)好的常見(jiàn)的面試題
項(xiàng)目亮點(diǎn)
- 使用分布式Session,可以實(shí)現(xiàn)讓多臺(tái)服務(wù)器同時(shí)可以響應(yīng)
- 使用redis做緩存提高訪問(wèn)速度和并發(fā)量,減少數(shù)據(jù)庫(kù)壓力,利用內(nèi)存標(biāo)記減少redis的訪問(wèn)
- 使用頁(yè)面靜態(tài)化,加快用戶訪問(wèn)速度,提高QPS,緩存頁(yè)面至瀏覽器,前后端分離降低服務(wù)器壓力
- 使用消息隊(duì)列完成異步下單,提升用戶體驗(yàn),削峰和降流
- 安全性優(yōu)化:雙重md5密碼校驗(yàn),秒殺接口地址的隱藏,接口限流防刷,數(shù)學(xué)公式驗(yàn)證碼
秒殺流程
一. 環(huán)境的搭建
二. 登錄功能的實(shí)現(xiàn)
主要內(nèi)容
- 數(shù)據(jù)庫(kù)設(shè)計(jì)
- 明文密碼兩次MD5處理
- JSR303參數(shù)檢驗(yàn)+全局異常處理
- 分布式Session
數(shù)據(jù)庫(kù)設(shè)計(jì)
- 不做注冊(cè),直接登錄,在MySQL中直接創(chuàng)建表
- 用戶表包括id、nickname、password、salt、頭像、注冊(cè)時(shí)間、上次登錄時(shí)間、登錄次數(shù)等字段
對(duì)登錄密碼進(jìn)行兩次MD5:細(xì)節(jié)描述
- 加密的目的:第一次是因?yàn)閔ttp是明文傳輸?shù)?#xff0c;第二次為了防止數(shù)據(jù)庫(kù)被盜
JSR303參數(shù)檢驗(yàn)
- 通過(guò)對(duì)輸入的參數(shù)LoginVo加注解@validated,然后在傳入的參數(shù)mobile和password上加上注解判斷,如@NotNull判斷是否為空,也可以自定義
- 全局異常處理
分布式Session?
三. 實(shí)現(xiàn)秒殺功能
- 數(shù)據(jù)庫(kù)設(shè)計(jì)
- 商品列表頁(yè)
- 商品詳情頁(yè)
- 訂單詳情頁(yè)
- 包括商品表、商品表訂單表、秒殺商品表、秒殺商品訂單表
- 為了展示秒殺商品的詳情需要goods和miaosha_goods中的信息,所以封裝一個(gè)GoodsVo,包括價(jià)格、庫(kù)存、秒殺起始時(shí)間
商品詳情頁(yè)
訂單詳情頁(yè)
- 這里也是秒殺功能的實(shí)現(xiàn)
- 在控制層先判斷庫(kù)存、然后判斷訂單是否存在、如果都沒(méi)有就下單
- 下單順序?yàn)闇p庫(kù)存、下定單、寫(xiě)入秒殺訂單
四. JMeter壓測(cè)
- JMeter入門
- 自定義變量模擬多個(gè)用戶
- JMeter命令行使用
- Redis壓測(cè)工具redis-benchmark
- Spring Boot打war包
- JMeter在windows下是圖形界面
- 打開(kāi)jmeter.bat運(yùn)行圖形界面
- 測(cè)試計(jì)劃中添加線程組
- 在線程組中添加HTTP請(qǐng)求默認(rèn)值(就是端口號(hào))
- 在線程組中添加HTTP請(qǐng)求(就是要測(cè)試的類的URL)、這里可以設(shè)置帶參數(shù)
- 在線程組中添加監(jiān)聽(tīng)器進(jìn)監(jiān)聽(tīng)
- 也可以通過(guò)自定義模擬多用戶(寫(xiě)一個(gè)文件,導(dǎo)入即可)
- JMeter在Linux下是命令行進(jìn)行操
- 在Windows上錄好jmx
- 命令行:sh jmeter.sh -n -t XXX.jmx -l result.jtl
- 把result.jtl導(dǎo)入到j(luò)meter
- 結(jié)果是五千并發(fā)的情況下,QPS為一千三左右
- redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
- -c為100個(gè)并發(fā)連接,-n為100000個(gè)請(qǐng)求
- Redis的QPS在十萬(wàn)左右
五. 頁(yè)面優(yōu)化技術(shù)
內(nèi)容
- 頁(yè)面緩存+URL緩存+對(duì)象緩存
- 頁(yè)面靜態(tài)化,前后端分離
- 靜態(tài)資源優(yōu)化
- CDN優(yōu)化
頁(yè)面緩存+URL緩存+對(duì)象緩存
- 秒殺的瓶頸在于數(shù)據(jù)庫(kù),所以要加上各種粒度的緩存,最大的是頁(yè)面緩存、最小的是對(duì)象緩存
- 頁(yè)面緩存步驟(這里指的是商品列表):
- 從redisService中取緩存
- 若緩存中沒(méi)有則手動(dòng)渲染,利用thymeleaf模板
- 然后將頁(yè)面加入緩存,并返回渲染頁(yè)面
- 不宜時(shí)間太長(zhǎng),設(shè)置為60s即可
- URL緩存(指的是商品詳情頁(yè))
- 與頁(yè)面緩存步驟基本一致,但是需要取緩存和加緩存時(shí)要加入?yún)?shù),GoodsId
- 對(duì)象緩存(指的是User對(duì)象)
- 前面的頁(yè)面緩存和URL緩存適合變化不大的,緩存時(shí)間比較短
- 對(duì)象緩存是長(zhǎng)期緩存,所以需要有個(gè)更新的步驟
- 第一步是取緩存
- 若緩存中沒(méi)有則去數(shù)據(jù)庫(kù)中查找,并加入緩存;如數(shù)據(jù)庫(kù)中沒(méi)有就報(bào)錯(cuò)
- 更新用戶的密碼
- 加緩存之后的QPS大概3000
- 需要先更新數(shù)據(jù)庫(kù),后刪除緩存;順序不能反,會(huì)導(dǎo)致數(shù)據(jù)不一致:若線程1先刪除緩存,然后線程2讀操作,發(fā)現(xiàn)緩存中沒(méi)有,把數(shù)據(jù)庫(kù)中的舊數(shù)據(jù)加入緩存,然后線程1更新數(shù)據(jù)庫(kù),就會(huì)導(dǎo)致緩存與數(shù)據(jù)庫(kù)數(shù)據(jù)不一致
頁(yè)面靜態(tài)化,前后端分離
- 頁(yè)面靜態(tài)化無(wú)非就是使用純html頁(yè)面+Ajax請(qǐng)求json數(shù)據(jù)后再填充頁(yè)面
- 若A頁(yè)面跳轉(zhuǎn)到B頁(yè)面之前需要條件判斷可以先在A頁(yè)面中利用ajax請(qǐng)求判斷后再跳轉(zhuǎn)
- 如果不需要條件判斷可以直接跳轉(zhuǎn)到B的靜態(tài)頁(yè)面,讓B自己用ajax請(qǐng)求數(shù)據(jù)
防超賣
- 發(fā)生在減庫(kù)存的時(shí)候
- 解決方法是在Update語(yǔ)句中加一個(gè)判斷
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-pBQCw6T3-1596466141006)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/752fa7ac-cacc-4761-90a7-36803e689e22/Untitled.png)]
- 還有一種情況是一個(gè)用戶同時(shí)發(fā)了兩個(gè)請(qǐng)求,假如庫(kù)存充足,且沒(méi)有訂單生成,那么就會(huì)減兩次庫(kù)存
- 解決辦法是建立用戶和商品的唯一索引
- 做到以上兩點(diǎn)是不會(huì)發(fā)生超賣的
CDN優(yōu)化
- CDN是內(nèi)容分發(fā)網(wǎng)絡(luò),相當(dāng)于緩存,只是部署在全國(guó)各地,當(dāng)用戶發(fā)起請(qǐng)求時(shí),會(huì)找最近的CDN獲取資源
- **總結(jié):**并發(fā)大的瓶頸在于數(shù)據(jù)庫(kù),所以解決辦法是加各種緩存:從瀏覽器開(kāi)始,做頁(yè)面的靜態(tài)化,將靜態(tài)頁(yè)面緩存在瀏覽器中;請(qǐng)求到達(dá)網(wǎng)站之前可以部署一些CDN,讓請(qǐng)求首先訪問(wèn)CDN;然后是頁(yè)面緩存、URL緩存、對(duì)象緩存;
- 加緩存的缺點(diǎn):數(shù)據(jù)可能不一致,只能做一個(gè)平衡
六. 接口優(yōu)化
內(nèi)容
- Redis預(yù)減庫(kù)存減少數(shù)據(jù)庫(kù)訪問(wèn)
- 內(nèi)存標(biāo)記減少Redis訪問(wèn)
- RabbitMQ隊(duì)列緩沖,異步下單,增強(qiáng)用戶體驗(yàn)
- RabbitMQ安裝與Spring Boot集成
- 訪問(wèn)Nginx水平擴(kuò)展
- 壓測(cè)
思路:減少數(shù)據(jù)庫(kù)訪問(wèn)
- 系統(tǒng)初始化,把商品庫(kù)存數(shù)量加載到Redis中
- 收到請(qǐng)求,Redis預(yù)減庫(kù)存,庫(kù)存不足,直接返回,否則進(jìn)入3
- 請(qǐng)求入隊(duì),立即返回排隊(duì)中
- 請(qǐng)求出隊(duì),生成訂單,減少庫(kù)存
- 客戶端輪詢,是否秒殺成功
優(yōu)化思路
- 系統(tǒng)初始化,把商品庫(kù)存數(shù)量加載到Redis
- 收到請(qǐng)求,Redis預(yù)減庫(kù)存,庫(kù)存不足,直接返回,否則進(jìn)入3
- 請(qǐng)求入隊(duì),立即返回排隊(duì)中(異步下單)
- 請(qǐng)求出隊(duì),生成訂單,減少庫(kù)存,把訂單寫(xiě)入Redis中
- 客戶端輪詢,判斷是否秒殺成功
秒殺接口優(yōu)化
- 之前的沒(méi)有庫(kù)存預(yù)熱的步驟是:查庫(kù)存-查訂單-修改庫(kù)存-生成訂單
- 系統(tǒng)初始化時(shí)把庫(kù)存加載到數(shù)據(jù)庫(kù):MiaoshaController 繼承InitializingBean實(shí)現(xiàn)afterPropertiesSet方法即可
- 在上一步庫(kù)存預(yù)熱之后,執(zhí)行步驟為:查Redis庫(kù)存-判斷是否存在訂單-進(jìn)入隊(duì)列-在出隊(duì)時(shí)才對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作
- 這一步還可以有一個(gè)優(yōu)化,就是內(nèi)存標(biāo)記,使用一個(gè)Map,將商品ID設(shè)置為false,當(dāng)買空時(shí),設(shè)為true;然后每次不是直接訪問(wèn)Redis進(jìn)行庫(kù)存查詢,而是對(duì)商品ID進(jìn)行條件判斷
- 內(nèi)存標(biāo)記的優(yōu)點(diǎn)是減少對(duì)Redis的訪問(wèn)(當(dāng)商品已經(jīng)賣完之后)
七. 安全優(yōu)化
主要內(nèi)容
- 秒殺接口地址隱藏
- 數(shù)學(xué)公式驗(yàn)證碼
- 接口限流防刷
秒殺接口地址隱藏
- 雖然前端頁(yè)面在秒殺未開(kāi)始時(shí)秒殺按鈕設(shè)置為不可用,但是有可能用戶通過(guò)前端js代碼找到秒殺地址在秒殺未開(kāi)始時(shí)直接訪問(wèn),秒殺接口隱藏的目的是用戶通過(guò)js獲取到的秒殺地址并不能讓其完成秒殺功能
- 在秒殺之前要先通過(guò)Controller中的/path路徑下的類隨機(jī)生成一個(gè)path,然后和用戶ID一起存入Redis,在執(zhí)行秒殺的時(shí)候再?gòu)腞edis中取Path進(jìn)行驗(yàn)證,然后進(jìn)行秒殺
數(shù)學(xué)公式驗(yàn)證碼
- 作用:接口防刷;錯(cuò)開(kāi)請(qǐng)求
- 在獲取Path是進(jìn)行驗(yàn)證
接口限流防刷
- 當(dāng)一個(gè)用戶訪問(wèn)接口時(shí),把訪問(wèn)次數(shù)寫(xiě)入緩存,并設(shè)置有效期
- 一分鐘之內(nèi)如果用戶訪問(wèn),則緩存中的訪問(wèn)次數(shù)加一,如果次數(shù)超限進(jìn)行限流操作
- 如果一分鐘內(nèi)沒(méi)有超限,緩存中數(shù)據(jù)消失,下次再訪問(wèn)時(shí)重新寫(xiě)入緩存
使用一個(gè)通用攔截器
- 首先寫(xiě)一個(gè)注解AccessLimit
- 后面每個(gè)類只需要加注解即可設(shè)置防刷次數(shù)
- 定義攔截器:繼承HandlerInterceptorAdapter類
八. 總結(jié)
1.問(wèn)題總結(jié)(主要從三個(gè)方面:項(xiàng)目本身的問(wèn)題、可能出現(xiàn)的問(wèn)題、可改進(jìn)的地方)
1.1 項(xiàng)目本身的問(wèn)題
- 畫(huà)一下項(xiàng)目的架構(gòu)圖
- 講一下秒殺流程
- 秒殺模塊怎么設(shè)計(jì)的
- 秒殺部分是怎么做的
- 分布式Session是怎么實(shí)現(xiàn)的
- 如何解決超賣?mysql鎖
- 如何解決重復(fù)下單?mysql唯一索引
- 如何防刷?驗(yàn)證碼+通用攔截器限流
- 消息隊(duì)列的作用?異步削峰
- 壓測(cè)沒(méi)有?用什么壓測(cè)?QPS是多少?
- 庫(kù)存預(yù)減用的是哪個(gè)redis方法
1.2 可能出現(xiàn)的問(wèn)題
- 緩存和數(shù)據(jù)庫(kù)數(shù)據(jù)一致性如何保證?
- 如果項(xiàng)目中的redis服務(wù)掛掉,如何減輕數(shù)據(jù)庫(kù)的壓力
- 假如減了庫(kù)存但用戶沒(méi)有支付,怎么將庫(kù)存還原繼續(xù)進(jìn)行搶購(gòu)
1.3 可改進(jìn)的地方
- 系統(tǒng)瓶頸在哪?如何查找,如何再優(yōu)化?
- 除了你項(xiàng)目里面的優(yōu)化,你還有什么優(yōu)化策略嗎?(同上一個(gè)問(wèn)題)
- 使用了大量緩存,那么就存在緩存擊穿和緩存雪崩以及緩存一致性等問(wèn)題
- 大量的使用緩存,對(duì)于緩存服務(wù)器也有很大的壓力,如何減少redis的訪問(wèn)
- 在高并發(fā)請(qǐng)求的業(yè)務(wù)場(chǎng)景,大量請(qǐng)求來(lái)不及處理,甚至出現(xiàn)請(qǐng)求堆積的情況
- 怎么保證一個(gè)用戶不能重復(fù)下單
- 怎么解決超賣現(xiàn)象
- 頁(yè)面靜態(tài)化的過(guò)程
常見(jiàn)問(wèn)題的回答
總結(jié)
以上是生活随笔為你收集整理的秒杀项目总结及面试常见问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 程序人生丨程序员必会的 10 种核心技能
- 下一篇: Logistic 回归与 Softmax