Java秒殺系統優化的工程要點

這篇博客是筆者學習慕課網若魚老師的《Java秒殺系統方案優化 高性能高併發實戰》課程的學習筆記。若魚老師授課循循善誘,講解由淺入深,歡迎你們支持。html

本文記錄課程中的注意點,方便之後code review。此外,本文將注意點相關的優質講解連接在了一塊兒,方便初學者系統學習。前端

本文並不是單純介紹秒殺系統特有的技術點,不適合高手。進階學習的話,極客時間有個不錯的小專欄——如何設計一個秒殺系統,阿里高級技術專家講解秒殺系統的設計要點,那個課程挺乾貨的。java

設計秒殺系統的技術要點

1. 登陸的密碼傳輸:

用戶的數據庫表設計,須要增長一字段保存密碼的Salt值mysql

兩次MD5操做(敏感數據必定要使用https協議傳輸):nginx

  • 客戶端:將明文password和客戶端硬編碼的Salt值進行拼接,而後進行MD5操做。

不用鹽的話,MD5字符串有可能會被彩虹表或者社工庫破解程序員

  • 服務端:將客戶端傳過來的MD5字符串和數據庫用戶對應的Salt字段進行拼接。而後進行MD5操做。

此次加鹽MD5,能夠有效防止內部員工泄露或者數據庫被拖庫後,明文密碼泄露算法

2. 自定義JSR303的校驗器

能夠參照javax.validation.constraints.NotNull註解,自定義本身的校驗器,將校驗代碼與業務代碼分離。不過因爲校驗失敗會輸出BindException異常,因此最好配合全局捕獲異常進行友好的輸出。sql

自定義校驗器很簡單,只須要定義一個註解和對應的校驗類數據庫

3. 自定義全局異常捕獲

使用@ControllerAdvice註解,定義全局的異常捕獲,並從異常中獲取異常信息解析出來,發送給前端
能夠自定義一個GlobalException異常,利用全局異常捕獲,將全部服務器處理異常集中處理。(Service層處理異常後不設置狀態碼,而是直接拋GlobalException全局異常)後端

不返回狀態碼的好處是Controller層不須要再繁瑣的判斷Service層的返回值,代碼更簡潔

4. 數據庫表設計

  • 經過將訂單創建惟一索引來保證用戶只能建立一個秒殺訂單
  • 商品金額最好以分爲單位,比較安全
  • 商品ID最好不要使用自增,會暴露商品總數等信息。可使用UUID,但範圍查找時會有性能損耗。因此通常採用SnowFlake算法生成ID

另外,自增ID的缺點也就是沒法在多個表中,或者多個數據庫中保持ID主鍵惟一不重複,因此如果使用分佈式數據庫以及數據合併的狀況下時不能使用自增ID的。

5. 代碼規範

  • 更新字段越多,產生的數據庫Binlog就越多。因此只更新數據庫部分字段的時候,最好新建一個對象,只賦值要更新的字段,而後調用mybatis的@Update,這樣不作全量更新能夠提升性能
  • 前端回包使用Result包裝類封裝,對報錯信息使用CodeMsg包裝類封裝,保持代碼風格統一
  • Service只注入跟本身同名的dao,若是須要別的dao,請注入對應的Service

Service的api相比dao會多一些防護代碼(例如,直接修改了別的模塊dao數據,但緩存未清理),更加安全

6. 事務

秒殺有兩個事務:

  1. 減庫存->建立秒殺訂單
  2. 建立秒殺訂單
    秒殺中涉及到上述兩個事務,爲了保障數據安全,可使用聲明式事務(Spring的@Transactional)

PROPAGATION_REQUIRED是Spring默認的傳播機制,若是外層有事務,則當前事務加入到外層事務,一塊提交,一塊回滾。本工程的場景使用默認事務傳播機制便可

有關Spring事務傳播機制能夠查看這篇博客

7. 壓測

  • 在生產環境中,秒殺系統要獨立運行與其餘業務系統,實現資源隔離,避免業務系統相互影響穩定性
  • 請求入口可使用nginx,LVS,F5等不一樣的負載均衡器
  • Jmeter 隨機生成用戶數據,而後使用Jmeter模擬用戶壓測。壓測運行環境最好與被測服務器環境隔離。

接口測試能夠還使用Postman和ApacheBench

8. 頁面優化技術

  • 頁面/URL緩存。用於數據變化不頻繁的頁面或者熱點網頁。若是數據較多須要分頁的數據,相似商品詳情數據,通常能夠考慮只緩存前兩頁(根據訪問量做取捨)

緩存方法:將渲染好的html文件存放到Redis。在訪問Url時,首先檢測Redis是否有html緩存。有緩存的話則直接返回緩存;沒有緩存的話則渲染後存入Redis,並返回給前端。頁面緩存過時時間具體根據業務場景判斷。

  • 頁面局部緩存。熱點數據緩存,當Ajax請求信息更新,涉及的多是須要保存在數據庫的操做,例如表格信息等時,能夠採用Redis緩存,方法同頁面緩存同樣,定義好能夠區分業務的Key便可

  • 靜態資源優化
    • JS/CSS壓縮,減小流量(可經過升級HTTP2來解決)
    • 多個JS/CSS組合,減小鏈接數(例如:tengine)
    • CDN就近訪問

若是須要採用JS/CSS壓縮或者減小鏈接數等方法,能夠使用HTTP2來提高性能

  • 對象緩存。例如使用Redis保存Session對象。對象緩存涉及到一個雙寫一致性問題,有關雙寫一致性問能夠查看這篇博客

9. 秒殺的邏輯優化

順序:

  1. 系統初始化,把商品庫存數量加載到Redis
  2. 收到請求,Redis原子操做預減庫存,庫存不足,直接返回,不然進入3
  3. 請求入隊,當即返回前端「排隊中」
  4. 請求出隊,生成訂單,減小庫存(服務端)
  5. 客戶端輪詢,是否秒殺成功(客戶端)和4同步,獲得結果刷新結果顯示

優化:

  1. 在第二步預減庫存時,能夠在內存里加一個map,ID爲商品ID,value爲是否有庫存,這樣當庫存沒有以後,直接經過內存中的值判斷是否還有庫存,減小對Redis的訪問。
  2. 購買請求加入消息隊列,異步下單(前端顯示排隊中),加強用戶體驗
  3. 前端要儘可能減小重複請求

10. 安全優化

10.1 秒殺接口地址隱藏

  1. 每次點擊秒殺按鈕,先從服務器獲取動態拼接而成的秒殺地址。
  2. Redis以緩存用戶ID和商品ID爲Key,秒殺地址爲Value緩存秒殺地址
  3. 用戶請求秒殺商品的時候,要帶上秒殺地址進行校驗

10.2 數學公式驗證碼

  1. 防止惡意腳本搶購
  2. 使請求時間分散

10.3 接口限流防刷

使用計數法,在攔截器作限制請求頻率。利用Redis緩存的有效期(以用戶ID拼接Url做爲key,以訪問次數爲value),指定緩存有效期爲1秒,訪問接口每次將value+1,到達閾值跳轉全局異常。

優化:使用攔截器+自定義註解,減小對業務代碼的侵入。有關攔截器能夠查看這篇博客
另外對於接口限流也能夠考慮使用令牌桶,控制對mysql的訪問。

最後,限於筆者經驗水平有限,歡迎讀者就文中的觀點提出寶貴的建議和意見。若是想得到更多的學習資源或者想和更多的技術愛好者一塊兒交流,能夠關注個人公衆號『全菜工程師小輝』後臺回覆關鍵詞領取學習資料、進入先後端技術交流羣和程序員副業羣。同時也能夠加入程序員副業羣Q羣:735764906 一塊兒交流。

哎呀,若是個人名片丟了。微信搜索「全菜工程師小輝」,依然能夠找到我

相關文章
相關標籤/搜索