MySQL建表添加乐观锁字段_Java秒杀系统优化-Redis缓存-分布式session-RabbitMQ异步下单-页面静态化...
Java秒殺系統優化-Redis緩存-分布式session-RabbitMQ異步下單-頁面靜態化
項目介紹
基于SpringBoot+Mybatis搭建的秒殺系統,并且針對高并發場景進行了優化,保證線程安全的同時極大地提高了服務器的吞吐量,主要優化手段有頁面靜態化、Redis緩存(頁面緩存、對象緩存)、RabbitMQ異步下單,項目實現的主要功能為 登錄-->商品列表瀏覽-->秒殺-->付款或返回。本項目利用壓測工具Jmeter對優化前后的性能做了詳細的評測,附帶完整的測試報告以及整個系統的設計思路報告。
軟件說明
整個項目的結構為嚴格的Maven項目結構,源碼包下的包名即為其主要作用的縮寫名,易于理解,概不贅述。
本文重點闡述計思路及優化方案,并附在Jmeter下壓測的完整結果報告。
設計方案
以下分功能模塊闡述:
一、登錄功能及session
登錄的設計并不復雜,主要思路為兩次MD5+鹽化,首先為前端用固定salt+password做一次MD5然后傳至后端,后端邏輯首先對用戶存在與否做判斷,然后取出該用戶的salt值,和前端傳來的已經一次MD5的password再次MD5,然后比較即可,然后生成session并存儲在redis緩存中,返回cookie。值得注意的是在user表中加入隨機的salt可極大降低彩虹表攻擊的風險,還有就是將session值存儲于redis緩存中也極大地降低了對數據庫的訪問。
二、商品列表、訂單詳情、商品詳情功能
這部分功能邏輯十分簡單,即為查數據,然后展示,無須贅述,需注意的是,這部分功能的優化策略,詳見后文。
三、秒殺功能
該功能為系統的核心功能,既要保證程序的線程安全性,又要滿足高并發場景的需求。
在未優化前,其主要的邏輯為:查庫存-->查是否秒殺-->秒殺,又因為該邏輯理論上應為一個原子的操作,所以加鎖,或者作為事務,但是這樣會大大影響程序的并發性能,所以需做優化。
四、數據庫
數據庫的主要設計為五張表:goods、miaosha_goods、miaosha_user、miaosha_order、order_info
具體數據表的主要結構,在./sql/中有sql文件,并且./java/util/中有用于生成測試用戶的腳本,有興趣的讀者可以查看。
以上即為主要功能,接下來闡述對應的一些優化手段。
優化方案
一、頁面緩存
頁面緩存的主要思路為,將一些用戶經常請求的頁面,例如/goods/to_list--商品列表頁面,存儲到redis緩存中,在用戶請求的時候直接在緩存中獲取并返回,如果取緩存失敗,則利用thymeleaf的手動渲染,渲染后存入緩存,并且返回。我們可以很明顯的知道,不使用頁面緩存的請求,每次都先訪問數據庫,然后經thymeleaf渲染,然后返回,其中渲染的過程可能需要從磁盤中讀取html模板,而使用頁面緩存以后,直接在內存緩存中讀取,無需查庫和渲染,只有失效的情況下才需要查庫渲染,所以在些用戶經常請求的頁面中使用頁面緩存優化,可大大降低對數據庫和服務器的壓力。(需要注意的是合理的設置頁面緩存的有效期)。
二、對象緩存
相對于頁面緩存,對象緩存是個更細粒度的緩存,比如說在登錄模塊中的session中,我們把session對應的user對象存儲到redis緩存中,那么在需要user對象的頁面中,既不需要登錄,也不需要更具cookie去查找數據庫,只需要通過cookie在redis中獲取user對象,即可使用,同理,這樣類型的緩存也會減小對數據庫的壓力。
三、頁面靜態化
上述的兩種緩存,都是利用redis緩存服務器來實現的,雖然可以降低對數據庫和服務器的壓力,但是,redis服務器的容量和處理能力也是有限的,所以我們可以考慮將頁面模板直接緩存到用戶的瀏覽器,那么每次請求用戶只需要請求用于渲染的對象即可,這不僅僅減輕了redis服務器的壓力,同時也減少了帶寬的消耗,此即為頁面靜態化。
在本項目中,主要實現的是商品詳情、訂單詳情頁面、秒殺頁面的靜態化,主要方法是利用ajax的異步加載,請求渲染需要的對象,并且通過配置
####### spring.resources的相關參數來告訴瀏覽器是否緩存,緩存有效時間等等。
四、靜態資源優化
主要手段包括JS/CSS壓縮,CDN等,此項目中并沒有嘗試,但不失為優化的另外一些好的思路。
以上部分的優化手段主要為緩存、頁面優化等于前端比較接近的手段,對于后端接口的優化將在以下部分闡述
五、接口優化
主要思路為Redis預減庫存+RabbitMQ異步下單
具體流程如下:
1、系統初始化,加載庫存到redis緩存
2、收到請求,預減庫存
3、判斷庫存,若剩余,則入隊列,否則秒殺失敗
4、出隊下單
分析:
一、通過將庫存加載到redis中,使得每次判斷、減少庫存直接從內存中讀取,無需訪問數據庫
二、收到請求預見庫存,然后判斷
注意這一順序非常重要,保證了線程安全
分析:因為redis封裝的decr()等函數是線程安全的,無需外加同步,所以你通過decr()減少庫存后獲取到的庫存永遠都是你剛剛減少后得到的庫存,本身就是個原子操作,不會存在線程安全問題,然后根據這個庫存來入隊,不符合條件的秒殺請求直接返回失敗,極大地減少了服務器的壓力,而且整個后臺邏輯中,需要保證原子性的也僅僅是decr()這一個操作,并且由于redis經過了樂觀鎖優化,所以整個系統的并發性相對于自己首先同步代碼而言,并發性得到了極大的提高。
三、完成了上述的操作,再去實現接下來的邏輯就很簡單了,唯一需要注意的是,從隊列中出來的請求執行秒殺過程是一個事務,需完整執行,否則回滾。
同時,訂單的詳情頁面做一個靜態化優化,前端輪詢秒殺結果,得到結果后進行渲染即可。
以上即為接口優化的闡述,接下來是壓測報告。
測試參數
服務器:(Mysq、Redis、RabbitMQ等服務也均安裝在本機上
CPU: Hasse/戰神Z7m Intel-i7-6700HQ 2.6GHz-3.5Ghz 四核心八線程 三級cache 6M
內存: 8G DDR3L
磁盤: 5400轉/s 1TB
Java版本:1.8.0_161 Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
MySQL:5.7.20-log MySQL Community Server (GPL)
Redis:4.0.10 容量--100M
RabbitMQ:3.7.7
Mybatis、Druid等具體配置參數見./resource/application.properties文件
測試方法
測試工具使用Jmeter并發測試工具,為保證無非相關變量的影響,每次測試的并發線程數均設為1000,并發時間1s,并發循環次數為10次,如圖所示:
/goods/to_list接口測試
該接口為商品列表接口,對該接口做了頁面緩存的優化,分別對優化前,優化后做壓測,結果如下:
優化前的測試結果
優化后的測試結果
對于上圖的兩個測試,我們比較關注的是聚合報告里的 ThoughPut 的值,其值可以作為吞吐量的一個較好估計。
由圖可見:
優化前,系統吞吐量為:921.1/sec
優化后,系統吞吐量為:4046.9/sec
也就意味著,加速比為:4.39。
/goods/detail/{goodsId}接口測試
該接口為商品詳情接口,對該接口實現了頁面靜態化的處理,分別對優化前,優化后做壓測,結果如下:
優化前的測試結果
優化后的測試結果
對比以上兩圖,會發現,似乎區別并不明顯,很容易讓人得出優化無效的結論,其實不然
據本人分析,這種情況應該是由Jmeter自身導致的,因為Jmeter做測試的時候并不會依賴于其他瀏覽器,只是發起http請求,而瀏覽器所具備的一些功能,他并沒有,比如,緩存,所有,利用Jmeter進行測試并不能得出一個如意的結果,但是,我們可以通過瀏覽器來大致的了解頁面靜態化后的一些改變。如圖:
可以清楚的看到,這個頁面大部分的內容都被“已緩存”,只有400個字節左右的對象被請求并傳輸,這也就達到了我們優化前的目的了。
秒殺接口優化
對于接口測試,我實現準備了1000個user和cookie,具體腳本見./java/util/下代碼,在Jmeter中使用參數方法可自行百度,如圖:
對于秒殺接口,我們只對優化后的接口進行壓測,測試的情況分為兩類:
1、秒殺庫存充足
2、秒殺庫存不足
對于秒殺庫存充足的情況,我們設置初始的庫存為200,然后,并發量和其他測試設置一致,結果如圖:
對于秒殺庫存不足的情況,我們設置初始的庫存為0,然后,并發量和其他測試設置一致,結果如圖:
對于處理邏輯比較復雜的接口而言,最好和最壞的情況能達到這樣的一個吞吐量,同時保證線程安全性,比較滿意。
以上為全部測試報告,讀者若有不明之處,歡迎提問。
總結
以上是生活随笔為你收集整理的MySQL建表添加乐观锁字段_Java秒杀系统优化-Redis缓存-分布式session-RabbitMQ异步下单-页面静态化...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 系统改名,linux改名命令
- 下一篇: 小皮面板有php环境吗,体验phpStu