Java開發面試:高併發秒殺系統如何設計與優化
如今處在一個大資料時代,應屆生找工作面試高階Java開發工程師時,經常會被問一些和大資料相關的問題,比如大資料處理問題、高併發處理問題、資料優化問題等,筆者曾經遇到兩個比較經典的問題,高併發秒殺系統的設計優化問題和大資料檔案排序問題。在這裡總結了高併發秒殺系統的設計和優化點。
面試官常問的問題有:
簡單說一下秒殺系統的設計思路?
你怎麼實現秒殺業務的?
你怎麼保證秒殺成功的?
秒殺操作的策略是什麼?
你使用的Redis有什麼用?
你為什麼使用Redis中介軟體?
你測試過你這個系統的抗壓能力麼?
你使用過什麼方法來測試你的系統併發量?
你覺得你這個系統還可以再優化麼?
你覺得你這個系統的瓶頸在哪裡?還可以在哪些方向做進一步優化?
很多面試官問的比較籠統,很少面試官問的比較具體,事先的這些問題,都要做好準備,筆者的準備思路是:
功能模組劃分=》秒殺策略=》自己的優化點=》工具測試抗壓=》別人提供的優化方法和秒殺策略
1 秒殺系統特點
- 秒殺業務簡單,賣家查詢,買家下訂單減庫存。
- 秒殺時網站訪問流量激增,出現峰值;
- 訪問請求數量遠大於實際需求量。
2 架構設計優化方案
1 秒殺系統架構設計優化
一個常規的秒殺系統從前到後,依次有:
前端瀏覽器秒殺頁面=》中間代理服務=》後端服務層=》資料庫層
根據這個流程,一般優化設計思路:將請求攔截在系統上游,降低下游壓力。
整體設計思路和優化點:
限流:遮蔽掉無用的流量,允許少部分流量流向後端。
削峰:瞬時大流量峰值容易壓垮系統,解決這個問題是重中之重。常用的消峰方法有非同步處理、快取和訊息中介軟體等技術。
非同步處理:秒殺系統是一個高併發系統,採用非同步處理模式可以極大地提高系統併發量,其實非同步處理就是削峰的一種實現方式。
記憶體快取:秒殺系統最大的瓶頸一般都是資料庫讀寫,由於資料庫讀寫屬於磁碟IO,效能很低,如果能夠把部分資料或業務邏輯轉移到記憶體快取,效率會有極大地提升。
可拓展:當然如果我們想支援更多使用者,更大的併發,最好就將系統設計成彈性可拓展的,如果流量來了,拓展機器就好了。像淘寶、京東等雙十一活動時會增加大量機器應對交易高峰。
訊息佇列:訊息佇列可以削峰,將攔截大量併發請求,這也是一個非同步處理過程,後臺業務根據自己的處理能力,從訊息佇列中主動的拉取請求訊息進行業務處理。
充分利用快取:利用快取可極大提高系統讀寫速度。
2詳細方案
2.1 前端方案
靜態資源快取:將活動頁面上的所有可以靜態的元素全部靜態化,儘量減少動態元素;通過CDN快取靜態資源,來抗峰值。
禁止重複提交:使用者提交之後按鈕置灰,禁止重複提交
使用者限流:在某一時間段內只允許使用者提交一次請求,比如可以採取IP限流
2.2 中間代理層
可利用負載均衡(例如反響代理Nginx等)使用多個伺服器併發處理請求,減小伺服器壓力。
2.3 後端方案
控制層(閘道器層)
限制同一UserID訪問頻率:儘量攔截瀏覽器請求,但針對某些惡意攻擊或其它外掛,在服務端控制層需要針對同一個訪問uid,限制訪問頻率。
服務層
當用戶量非常大的時候,攔截流量後的請求訪問量還是非常大,此時仍需進一步優化。
1.業務分離:將秒殺業務系統和其他業務分離,單獨放在高配伺服器上,可以集中資源對訪問請求抗壓。
2.採用訊息佇列快取請求:將大流量請求寫到訊息佇列快取,利用伺服器根據自己的處理能力主動到訊息快取佇列中抓取任務處理請求,資料庫層訂閱訊息減庫存,減庫存成功的請求返回秒殺成功,失敗的返回秒殺結束。
3.利用快取應對讀請求:對於讀多寫少業務,大部分請求是查詢請求,所以可以讀寫分離,利用快取分擔資料庫壓力。
4.利用快取應對寫請求:快取也是可以應對寫請求的,可把資料庫中的庫存資料轉移到Redis快取中,所有減庫存操作都在Redis中進行,然後再通過後臺程序把Redis中的使用者秒殺請求同步到資料庫中。
2.4 資料庫層
資料庫層是最脆弱的一層,一般在應用設計時在上游就需要把請求攔截掉,資料庫層只承擔“能力範圍內”的訪問請求。所以,上面通過在服務層引入佇列和快取,讓最底層的資料庫高枕無憂。
如果不使用快取來作為中間緩衝而是直接訪問資料庫的話,可以對資料庫進行優化,減少資料庫壓力。
對於秒殺系統,直接訪問資料庫的話,存在一個【事務競爭優化】問題,可使用儲存過程(或者觸發器)等技術繫結操作,整個事務在MySQL端完成,把整個熱點執行放在一個過程當中一次性完成,可以遮蔽掉網路延遲時間,減少行級鎖持有時間,提高事務併發訪問速度。
2.5 其他秒殺策略
減少硬體開銷的策略 :
策略1:訊息佇列快取請求,按照佇列模型取任務執行,秒殺完畢即終止到秒殺結束頁面。
策略2:使用陣列為併發請求隨機分配秒殺狀態(成功和失敗),然後將分配到失敗狀態的請求派發到秒殺失敗的頁面,分到成功狀態的使用者在慢慢的按順序執行秒殺操作;(如果處理失敗了可以利用日誌來查詢具體秒殺失敗的商品和使用者,執行補救措施或者從其他使用者中拿取一個來執行秒殺操作)
策略3:類似於策略2,不過是用陣列為使用者分配秒殺資格,將大流量的使用者限制為小流量的使用者,得到秒殺資格的去執行秒殺,得不到秒殺資格的跳到秒殺失敗頁面。
(分配狀態或分配秒殺資格的策略:(陣列狀態密度不同,由前到後逐漸稀疏,可以讓先到的在前面隨機分配,後到的在後面隨機分配)根據先到的時間)
3 案例:利用訊息中介軟體和Redis快取實現
Redis是一個分散式快取系統,支援多種資料結構,可利用Redis輕鬆實現一個強大的秒殺系統。
我們可以採用Redis 最簡單的key-value資料結構,用一個原子型別的變數值(AtomicInteger)作為key,把使用者id作為value,庫存數量便是原子變數的最大值。對於每個使用者的秒殺,我們使用 RPUSH key value插入秒殺請求, 當插入的秒殺請求數達到上限時,停止所有後續插入。
然後我們可以再啟動多個工作執行緒,使用 LPOP key 讀取秒殺成功者的使用者id,然後再操作資料庫做最終的下訂單減庫存操作。
當然,上面Redis也可以替換成訊息中介軟體如ActiveMQ、RabbitMQ等,也可以將快取和訊息中介軟體 組合起來,快取系統負責接收記錄使用者請求,訊息中介軟體負責將快取中的請求同步到資料庫。
(1)使用Redis中介軟體快取動態資源的好處?
提高訪問速度,減少對資料庫的連結的開啟、關閉,
(2)為什麼不用JVM記憶體而使用Redis作為快取呢?
JVM 記憶體較小,隔一段時間會自動進行垃圾回收。JVM和業務程式繫結在一起了,如果程式出錯,JVM也會停止,這樣就導致快取資料丟失。
如果使用Redis,除了快取比較大之外,還實現了快取資料和業務程式的分離,即使執行程式出現錯誤,也不會影響快取。
3 壓力測試
使用JMeter 壓測工具
下載、安裝、進入C:/JMeter/bin下面的jmeter.bat批處理檔案來啟動JMeter的視覺化介面,
進入測試計劃新增執行緒組: 設定執行緒數,迴圈次數,新增HTTP預設請求,伺服器名稱,IP,以及自己設定的攜帶引數
新增監聽器,存放測試結果:聚合報告,可以表格查詢、圖形結果、樹結果
點選執行-》啟動。
併發量:50W-100W 100W-500W