系統介紹
本系統是使用SpringBoot開發的高併發限時搶購秒殺系統,除了實現基本的登錄、查看商品列表、秒殺、下單等功能,項目中還針對高併發情況實現了系統緩存、降級和限流。
開發工具
IntelliJ IDEA + Navicat + Sublime Text3 + Git + Chrome
壓測工具
JMeter
開發技術
前端技術 :Bootstrap + jQuery + Thymeleaf
後端技術 :SpringBoot + MyBatis + MySQL
中間件技術 : Druid + Redis + RabbitMQ + Guava
秒殺優化方向
-
將請求儘量攔截在系統上游:傳統秒殺系統之所以掛,請求都壓倒了後端數據層,數據讀寫鎖衝突嚴重,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小,我們可以通過限流、降級等措施來最大化減少對數據庫的訪問,從而保護系統。
-
充分利用緩存:秒殺商品是一個典型的讀多寫少的應用場景,充分利用緩存將大大提高併發量
實現技術點
1. 兩次MD5加密
將用戶輸入的密碼和固定Salt通過MD5加密生成第一次加密後的密碼,再講該密碼和隨機生成的Salt通過MD5進行第二次加密,最後將第二次加密後的密碼和第一次的固定Salt存數據庫
好處:
- 第一次作用:防止用戶明文密碼在網絡進行傳輸
- 第二次作用:防止數據庫被盜,避免通過MD5反推出密碼,雙重保險
2. session共享
驗證用戶賬號密碼都正確情況下,通過UUID生成唯一id作爲token,再將token作爲key、用戶信息作爲value模擬session存儲到redis,同時將token存儲到cookie,保存登錄狀態
好處: 在分佈式集羣情況下,服務器間需要同步,定時同步各個服務器的session信息,會因爲延遲到導致session不一致,使用redis把session數據集中存儲起來,解決session不一致問題。
3. JSR303自定義參數驗證
使用JSR303自定義校驗器,實現對用戶賬號、密碼的驗證,使得驗證邏輯從業務代碼中脫離出來。
4. 全局異常統一處理
通過攔截所有異常,對各種異常進行相應的處理,當遇到異常就逐層上拋,一直拋到最終由一個統一的、專門負責異常處理的地方處理,這有利於對異常的維護。
5. 頁面緩存 + 對象緩存
- 頁面緩存:通過在手動渲染得到的html頁面緩存到redis
- 對象緩存:包括對用戶信息、商品信息、訂單信息和token等數據進行緩存,利用緩存來減少對數據庫的訪問,大大加快查詢速度。
6. 頁面靜態化
對商品詳情和訂單詳情進行頁面靜態化處理,頁面是存在html,動態數據是通過接口從服務端獲取,實現前後端分離,靜態頁面無需連接數據庫打開速度較動態頁面會有明顯提高
7. 本地標記 + redis預處理 + RabbitMQ異步下單 + 客戶端輪詢
描述:通過三級緩衝保護,1、本地標記 2、redis預處理 3、RabbitMQ異步下單,最後纔會訪問數據庫,這樣做是爲了最大力度減少對數據庫的訪問。
實現:
- 在秒殺階段使用本地標記對用戶秒殺過的商品做標記,若被標記過直接返回重複秒殺,未被標記才查詢redis,通過本地標記來減少對redis的訪問
- 搶購開始前,將商品和庫存數據同步到redis中,所有的搶購操作都在redis中進行處理,通過Redis預減少庫存減少數據庫訪問
- 爲了保護系統不受高流量的衝擊而導致系統崩潰的問題,使用RabbitMQ用異步隊列處理下單,實際做了一層緩衝保護,做了一個窗口模型,窗口模型會實時的刷新用戶秒殺的狀態。
- client端用js輪詢一個接口,用來獲取處理狀態
8. 解決超賣
描述:比如某商品的庫存爲1,此時用戶1和用戶2併發購買該商品,用戶1提交訂單後該商品的庫存被修改爲0,而此時用戶2並不知道的情況下提交訂單,該商品的庫存再次被修改爲-1,這就是超賣現象
實現:
- 對庫存更新時,先對庫存判斷,只有當庫存大於0才能更新庫存
- 對用戶id和商品id建立一個唯一索引,通過這種約束避免同一用戶發同時兩個請求秒殺到兩件相同商品
- 實現樂觀鎖,給商品信息表增加一個version字段,爲每一條數據加上版本。每次更新的時候version+1,並且更新時候帶上版本號,當提交前版本號等於更新前版本號,說明此時沒有被其他線程影響到,正常更新,如果衝突了則不會進行提交更新。當庫存是足夠的情況下發生樂觀鎖衝突就進行一定次數的重試。
9. 使用數學公式驗證碼
描述:點擊秒殺前,先讓用戶輸入數學公式驗證碼,驗證正確才能進行秒殺。
好處:
- 防止惡意的機器人和爬蟲
- 分散用戶的請求
實現:
- 前端通過把商品id作爲參數調用服務端創建驗證碼接口
- 服務端根據前端傳過來的商品id和用戶id生成驗證碼,並將商品id+用戶id作爲key,生成的驗證碼作爲value存入redis,同時將生成的驗證碼輸入圖片寫入imageIO讓前端展示
- 將用戶輸入的驗證碼與根據商品id+用戶id從redis查詢到的驗證碼對比,相同就返回驗證成功,進入秒殺;不同或從redis查詢的驗證碼爲空都返回驗證失敗,刷新驗證碼重試
10. 使用RateLimiter實現限流
描述:當我們去秒殺一些商品時,此時可能會因爲訪問量太大而導致系統崩潰,此時要使用限流來進行限制訪問量,當達到限流閥值,後續請求會被降級;降級後的處理方案可以是:返回排隊頁面(高峯期訪問太頻繁,等一會重試)、錯誤頁等。
實現:項目使用RateLimiter來實現限流,RateLimiter是guava提供的基於令牌桶算法的限流實現類,通過調整生成token的速率來限制用戶頻繁訪問秒殺頁面,從而達到防止超大流量沖垮系統。(令牌桶算法的原理是系統會以一個恆定的速度往桶裏放入令牌,而如果請求需要被處理,則需要先從桶裏獲取一個令牌,當桶裏沒有令牌可取時,則拒絕服務)
壓測效果
優化前 :開啓1000個線程循環10次同時訪問,QPS = 423 優化後:QPS = 2501
本項目是學習了imooc網視頻之後的個人理解和知識彙總,學習鏈接:https://coding.imooc.com/class/168.html