開篇詞 | 秒殺系統架構設計都有哪些關鍵點?
- 秒殺主要解決兩個問題,一個是併發讀,一個是併發寫
- 秒殺的總體架構須要作到:穩、準、快。
01 | 設計秒殺系統時應該注意的5個架構原則
-
架構原則:「4 要 1 不要」前端
- 數據要儘可能少
- 請求數要儘可能少
- 路徑要儘可能短
- 依賴要儘可能少
- 不要有單點
架構是一種平衡的藝術,而最好的架構一旦脫離了它所適應的場景,一切都將是空談。算法
02 | 如何才能作好動靜分離?有哪些方案可選?
那到底什麼纔是動靜分離呢?所謂「動靜分離」,其實就是把用戶請求的數據(如HTML頁面)劃分爲「動態數據」和「靜態數據」。數據庫
- 簡單來講,「動態數據」和「靜態數據」的主要區別就是看頁面中輸出的數據是否和URL、瀏覽者、時間、地域相關,以及是否含有Cookie等私密數據。
- 你應該把靜態數據緩存到離用戶最近的地方。靜態數據就是那些相對不會變化的數據,所以咱們能夠把它們緩存起來。緩存到哪裏呢?常見的就三種,用戶瀏覽器裏、CDN上或者在服務端的Cache中。你應該根據狀況,把它們儘可能緩存到離用戶最近的地方。
如何作動靜分離的改造瀏覽器
03 | 有針對性地處理好系統的熱點數據
-
發現熱點數據cookie
- 經過賣家報名的方式提早篩選出來,經過報名系統對這些熱點商品進行打標。
- 經過大數據分析來提早發現熱點商品,好比咱們分析歷史成交記錄、用戶的購物車記錄,來發現哪些商品可能更熱門、更好賣,這些都是能夠提早分析出來的熱點。
-
怎麼優化架構
- 優化熱點數據最有效的辦法就是緩存熱點數據,若是熱點數據作了動靜分離,那麼能夠長期緩存靜態數據。可是,緩存熱點數據更多的是"臨時」緩存,即無論是靜態數據仍是動態數據,都用一個隊列短暫地緩存數秒鐘,因爲隊列長度有限,能夠採用LRU淘汰算法替換。
- 再來講說限制。限制更多的是一種保護機制,限制的辦法也有不少,例如對被訪問商品的ID作一致性Hash,而後根據Hash作分桶,每一個分桶設置一個處理隊列,這樣能夠把熱點商品限制在一個請求隊列裏,防止因某些熱點商品佔用太多的服務器資源,而使其餘請求始終得不到服務器的處理資源。
- 最後介紹一下隔離。秒殺系統設計的第一個原則就是將這種熱點數據隔離出來,不要讓1%的請求影響另外的99%,隔離出也更方便對這1%的請求作針對性的優化。
04 | 流量削峯這事應該怎麼作?
對秒殺這個場景來講,最終可以搶到商品的人數是固定的,就說100人和10000人發起請求的結果都同樣,併發度越高,無效請求也越多。併發
可是從業務上來講,秒殺活動是但願更多的人來參與的,也就是開始以前但願有更多的人來刷頁面,但真正開始下單,秒請求並非越多越好。所以咱們能夠設計一些規則,讓併發的請求更多地延緩,並且咱們甚至能夠過濾掉一些無效請求。異步
-
有損方案
-
無損方案
- 排隊
- 消息隊列
- 線程池加鎖等待
- 把請求序列化到文件中,而後再順序地讀文件(例如基於MySQL binlog的同步機制)來恢復請求
秒殺系統中的經常使用削峯方法
-
答題
- 這個重要的功能就是把峯值的下單請求拉長,從之前的1s以內延長到2s-10s。還能防止機器搶單。
-
分層過濾
- 對請求進行分層過濾,從而過濾掉一些無效的請求。
- 瀏覽器層面:秒殺是否已經結束,答題是否正確
- 緩存:商品狀態是否正常,用戶是否具備秒殺資格,庫存判斷
- 數據:扣減庫存
05 | 影響性能的因素有哪些?又該如何提升系統的性能?
-
減小編碼
- 那麼如何才能減小編碼呢?例如,網頁輸出是能夠直接進行流輸出的,即用resp.getOutputstream() 函數寫數據,把一些靜態的數據提早轉化成字節,等到真正往外寫的時候再直接用OutputStream() 函數寫,就能夠減小靜態數據的編碼轉換。
-
減小序列化
- 序列化大部分是在RPC中發生的,所以避免或者減小RPC就能夠減小序列化,固然當前的序列化協議也已經作了不少優化來提高性能。有一種新的方案,就是能夠將多個關聯性比較強的應用進行"合併部署",而減小不一樣應用之間的RPC也能夠減小序列化的消耗。
- 所謂"合併部署",就是把兩個本來在不一樣機器上的不一樣應用合併部署到一臺機器上,固然不只僅是部署在一臺機器上,還要在同一個Tomcat容器中,且不能走本機的Socket,這樣才能避免序列化的產生。
-
併發讀優化
- 須要劃分紅動態數據和靜態數據分別進行處理:
像商品中的「標題"和"描述"這些自己不變的數據,會在秒殺開始以前全量推送到秒殺機器上,並一直緩存到秒殺結束;
- 像庫存這類動態數據,會採用"被動失效"的方式緩存必定時間(通常是數秒),失效後再去緩存拉取最新的數據。
06 | 秒殺系統「減庫存」設計的核心邏輯
- 下單減庫存,即當買家下單後,在商品的總庫存中減去買家購買數量。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種,下單時直接經過數據庫的事務機制控制商品庫存,這樣必定不會出現超賣的狀況。可是你要知道,有些人下完單可能並不會付款。
- 付款減庫存,即買家下單後,並不當即減庫存,而是等到有用戶付款後才真正減庫存,不然庫存一直保留給其餘買家。但由於付款時才減庫存,若是併發比較高,有可能出現買家下單後付т了款的狀況,由於可能商品已經被其餘人買走了。
- 預扣庫存,這種方式相對複雜一些,買家下單後,庫存爲其保留必定的時間(如10分鐘),超過這個時間,庫存將會自動釋放,釋放後其餘買家就能夠繼續購買。在買家付款前,系統會校驗該訂單的庫存是否還有保留:若是沒有保留,則再次嘗試預扣;若是庫存不足(也就是預扣失敗)則不容許繼續付款;若是預扣成功,則完成付款並實際地減去庫存。
針對「庫存超賣」這種狀況,在10分鐘時間內下單的數量仍然有可能超過庫存數量,遇到這種狀況咱們只能區別對待:對普通的商品下單數量超過庫存數量的狀況,能夠經過補貨來解決;可是有些賣家徹底不容許庫存爲負數的狀況,那隻能在買家付款時提示庫存不足。
實際使用方案
07 | 準備Plan B:如何設計兜底方案?
具體來講,系統的高可用建設涉及架構階段、編碼階段、測試階段、發佈階段、運行階段,以及故障發生時。
- 架構階段:架構階段主要考慮系統的可擴展性和容錯性,要避免系統出現單點問題。例如多機房單元化部署,即便某個城市的某個機房出現總體故障,仍然不會影響總體網站的運轉。
- 編碼階段:編碼最重要的是保證代碼的健壯性,例如涉及遠程調用問題時,要設置合理的超時退出機制,防止被其餘系統拖垮,也要對調用的返回結果集有預期,防止返回的結果超出程序處理範圍,最多見的作法就是對錯誤異常進行捕獲,對沒法預料的錯誤要有默認處理結果。
- 測試階段:測試主要是保證測試用例的覆蓋度,保證最壞狀況發生時,咱們也有相應的處理流程。
- 發佈階段:發佈時也有一些地方須要注意,由於發佈時最容易出現錯誤,所以要有緊急的回滾機制。
- 運行階段:運行時是系統的常態,系統大部分時間都會處於運行態,運行態最重要的是對系統的監控要準確及時,發現問題可以準確報警而且報警數據要準確詳細,以便於排查問題。
- 故障發生:故障發生時首先最重要的就是及時止損,例如因爲程序問題致使商品價格錯誤,那就要及B下架商品或者關閉購買連接,防止形成重大資產損失。而後就是要可以及時恢復服務,並定位緣由解決問題。
針對秒殺系統,如何作到高可用?
-
降級
- 所謂"降級」,就是當系統的容量達到必定程度時,限制或者關閉系統的某些非核心功能,從而把有限的資源保留給更核心的業務。它是一個有目的、有計劃的執行過程,因此對降級咱們通常須要有一套預案來配合執行。若是咱們把它系統化,就能夠經過預案系統和開關係統來實現降級。
- 降級方案能夠這樣設計:當秒殺流量達到5w/s時,把成交記錄的獲取從展現20條降級到只展現5條。「從20改到5"這個操做由一個開關來實現,也就是設置一個可以從開關係統動態獲取的系統參數。
-
限流
- 客戶端限流,好處能夠限制請求的發出,經過減小發出無用請求從而減小對系統的消耗。缺點就是當客戶端比較分散時,無法設置合理的限流閾值:若是閾值設的過小,會致使服務端沒有達到瓶頸時客戶端已經被限制;而若是的太大,則起т到限制的做用。
- 服務端限流,好處是能夠根據服務端的性能設置合理的閾值,而缺點就是被限制的請求都是無效的請求,處理這些無效的請求自己也會消耗服務器資源。
-
拒絕服務
- 在最前端的Nginx上設置過載保護,當機器負載達到某個值時直接拒絕HTTP請求並返回503錯誤碼,在Java層一樣也能夠設計過載保護。
- 拒絕服務能夠說是一種不得已的兜底方案,用以防止最壞狀況發生,防止因把服務器壓跨而長時間完全沒法提供服務。像這種系統過載保護雖然在過載時沒法提供服務,可是系統仍然能夠運做,當負載降低時又很容易恢復,因此每一個系統和每一個環節都應該設置這個兜底方案,對系統作最壞狀況下的保護。