秒殺系統架構設計-終章

流量削峯怎麼作

秒殺請求在時間上高度集中於某一特定的時間點。這樣一來,就會致使一個特別高的流量峯值,它對資源的消耗是瞬時的。java

可是秒殺場景,最終能搶到商品的人數是固定的,也就是說100和10000人發起請求結果是同樣的,併發度越高,無效請求也越多。mysql

可是從業務上來講,秒殺活動是但願更多的人來參與的,也就是開始以前但願有更多的人來刷頁面,開始下單時,能夠設計一些規則,讓併發的請求更多地延緩,過濾掉一些無效請求。nginx

爲何要削峯

服務器的處理資源是恆定的,出現峯值的話,很容易處理不過來,閒的時候卻又沒有什麼要處理。可是爲了保證服務質量,不少處理資源只能按照最忙的時候來預估,而這會致使資源的一個浪費。算法

針對秒殺這一場景,削峯從本質上來講就是更多地延緩用戶請求的發出,聽從「請求數要儘可能少」的原則。sql

削峯的一些操做思路:熔斷算法、答題、分層過濾。數據庫

分層過濾的核心思想是:在不一樣的層次儘量的過濾掉無效請求,讓「漏斗」最末端的纔是有效請求。而要達到這種效果,咱們就必須對數據作分層的校驗。分層校驗的基本原則是:緩存

  1. 將動態請求的讀數據緩存在WEB端,過濾掉無效的數據讀
  2. 對讀數據不作強一致性校驗,減小由於一致性校驗產生瓶頸的問題
  3. 對寫數據進行基於時間的合理分片,過濾掉過時的失效請求
  4. 對寫請求作限流保護,將超出系統承載能力的請求過濾掉
  5. 對寫數據進行強一致性校驗,只保留最後有效的數據

分層校驗的目的是:在讀系統中,儘可能減小因爲一致性校驗帶來的系統瓶頸,可是儘可能將不影響性能的檢查條件提早,如用戶是否具備秒殺資格、商品狀態是否正常、用戶答題是否正確、秒殺是否已經結束、是否非法請求、營銷等價物是否充足等;在寫數據系統中,主要對寫的數據(如「庫存」)作一致性檢查,最後在數據庫層保證數據的最終準確性。安全

影響性能的因素

服務端性能通常用QPS(Query Per Second,每秒請求數)來衡量,還有一個影響和QPS也息息相關,那就是響應時間(Response Time ,RT),它能夠理解爲服務器處理響應的耗時。服務器

正常狀況下響應時間(RT)越短,一秒鐘處理的請求數(QPS)天然也就會越多網絡

總QPS = (1000ms /響應時間)* 線程數量,這樣性能就和兩個因素相關,一個是一次響應服務端耗時,一個是處理請求的線程數

對於大部分的Web系統而言,響應時間通常都是由CPU執行時間和線程等待時間(好比RPC、IO等待、Sleep、Wait等)組成,即服務器在處理一個請求時,一部分是CPU自己在作運算,還有一部分是在各類等待。

線程數不是越多越好,由於線程自己也消耗資源。例如線程越多系統的線程切換成本就會越高,並且每一個線程也都會耗費必定內存。

通常默認配置,即「線程數=2*CPU核數+1」,一個根據最佳實踐出來的公式:

線程數=【(線程等待時間+線程CPU時間)/線程CPU時間】*CPU數量

如何發現瓶頸

就服務器而言,會出現瓶頸的地方不少,例如CPU、內存、磁盤以及網絡等均可能會致使瓶頸。此外,不一樣的系統對瓶頸的關注度也不同,例如對緩存系統而言,制約它的是內存,而對存儲型系統來講I/O更容易是瓶頸。咱們定位的場景是秒殺,它的瓶頸更多地發生在CPU上

CPU診斷工具能夠發現CPU的消耗,最經常使用的就是JProfiler和Yourkit這兩工具,它們能夠列出整個請求中每一個函數的CPU執行時間,能夠發現那個函數消耗的CPU時間最多,以便你有針對性地作優化。還能夠經過jstack定時地打印調用棧,若是某些函數調用頻繁或者耗時較多,那麼那些函數就會屢次出如今系統調用棧裏,這樣至關於採樣的方式也可以發現耗時較多的函數。

簡單判斷CPU是否是瓶頸?一個辦法就是看當QPS達到極限時,你的服務器的CPU使用率是否是超過了95%,若是沒有超過,那麼表示CPU還有提高的空間,要麼是有鎖限制,要麼是有過多的本地I/O等待發生。

如何優化系統

  1. 減小編碼
  2. 減小序列化
  3. java極致優化

    1. 直接使用Servlet處理請求。避免使用傳統的MVC框架,這樣能夠繞過一大堆複雜且用戶不大的處理邏輯,節省1ms時間
    2. 直接輸出流數據。使用resp.getOutputStream()而不是resp.getWriter()函數,能夠省掉一些不變字符數據的編碼,從而提高性能;數據輸出時推薦使用JSON而不是模板引擎來輸出頁面
  4. 併發讀優化

    1. 採用應用層LocalCache,即在秒殺系統的單機上緩存商品相關的數據。須要將數據劃分紅動態數據和靜態數據分別進行處理:

      1. 像商品中「標題」和「描述」這些自己不變的數據,會在秒殺開始以前全量推送到秒殺機器上,並一直緩存到秒殺結束;
      2. 像庫存這類動態數據,會採用「被動失效」的方式緩存必定時間(通常數秒),失效後再去緩存拉去最新的數據。

庫存這種頻繁更新的數據,一旦數據不一致,會不會致使超賣?

這就要用到前面介紹的讀數據的分層校驗原則,讀的場景能夠容許必定的髒數據,由於這裏的誤判只會致使少許本來無庫存的下單請求被誤認爲有庫存,能夠等到真正寫數據時再保證最終一致性,經過在數據的高可用性和一致性之間平衡,來解決高併發的數據讀取問題。

總結:首先是發現短板,其次是減小數據,兩個地方特別影響性能,一是服務端在處理數據時不可避免地存在字符到字節的相互轉化,二是HTTP請求時要作Gzip壓縮,還有網絡傳輸的耗時。再次,就是數據分級,也就是要保證首屏爲先、重要信息爲先,次要信息則異步加載,以這種方式提高用戶獲取數據的體驗。最後就是減小中間環節,減小字符到字節的轉換,增長預處理(提早作字符到字節的轉換)去掉不須要的操做。此外,要作好優化,你還需啊作好應用基線,好比性能基線(什麼時候性能忽然降低)、成本基線(活動用了多少臺機器)、鏈路基線(咱們的系統發生了那些變化),你能夠經過這些基線持續關注系統的性能,作到在代碼上提高編碼質量,在業務上改掉不合理的調用,在架構和調用鏈路上不斷的改進。

秒殺系統架構設計-減庫存

減庫存有哪幾種方式

用戶實際購買流程通常分爲兩步:下單和支付。咱們選擇在哪一個階段減小庫存是個問題

減庫存操做通常有以下幾個方式:

  • 下單減庫存
  • 付款減庫存
  • 預扣庫存

這3種方式都會有一些問題:

  1. 下單減庫存:競爭對手經過惡意下單的方式將改賣家的商品所有下單,那麼這款商品就不能正常售賣了。
  2. 付款減庫存:可能會出現買家成功下單,可是支付的時候提示庫存不足,影響用戶體驗
  3. 預扣庫存:下單之後預扣庫存,設置超時時間,在超時後,競爭對手仍是能夠再次下單

針對這種狀況,解決辦法仍是要結合安全和反做弊的措施來制止。例如,給常常下單不付款的賣家進行識別打標、給某些類目設置最大購買件數、以及對重複下單不付款的操做進行次數限制。

秒殺庫存的極致優化:

秒殺商品和普通商品的減庫存仍是有些差別的,例如商品數量比較少,交易時間段也比較短,咱們能夠大膽的用緩存來保存庫存,進行操做。

若是必須使用數據庫減小庫存,因爲mysql在處理同一個數據的時候會有大量線程來競爭InnoDB行鎖,而併發度越高時等待線程會越多,TPS(Transaction Per Second,每秒處理的消息數)會降低,RT(響應時間)會上升,數據庫的吞吐量就會嚴重受影響。

這時候單個熱點商品會影響整個數據庫的性能,致使0.01%的商品影響99.99%的商品的售賣,這是咱們不肯意看到的。一個思路是進行隔離,將熱點商品放到單獨的熱點庫中。可是維護上會比較麻煩,好比要作熱點數據的動態遷移以及單獨的數據庫等。

而分離熱點數據,仍然沒有解決併發鎖的問題,要解決併發鎖的問題,咱們能夠

  • 應用層排隊。按照商品維度設置隊列順序執行,這樣能減小同一臺機器對數據庫同一行記錄進行操做的併發度,同時也能控制單個商品佔用數據庫鏈接的數量,防止熱點商品佔用太多的數據庫鏈接
  • 數據庫層排隊。應用層只能作到單機的排隊,可是應用機器數自己不少,這種排隊方式控制併發的能力仍然有限,因此在數據庫層作全局排隊最理想。可使用patch,在數據庫層對單行記錄作到併發排隊。

排隊和鎖競爭都是要等待,可是有區別。mysql的innodb內部的死鎖檢測,以及mysql server和innodb的切換會比較消耗性能。

準備plan b,兜底方案

高可用建設須要考慮到系統建設的各個階段,也就是說它其實貫穿了系統建設的整個生命週期。

  1. 架構階段:架構階段主要考慮系統的可擴展性和容錯性,要避免系統出現單點問題。
  2. 編碼階段:編碼最重要的是保證代碼的健壯性,例如涉及遠程調用問題時,要設置合理的超時退出機制,防止被其餘系統拖垮,也要對調用的返回結果集有逾期,防止返回的結果超出程序處理範圍,最多見的作法就是對錯誤異常進行捕獲,對沒法預料的錯誤要有默認處理結果。
  3. 測試階段:測試主要是保證測試用例的覆蓋度,保證最壞狀況發生時,咱們也有響應的處理流程。
  4. 發佈階段:發佈出現錯誤,要有緊急的回滾機制
  5. 運行階段:運行時是系統的常態,系統大部分時間都會處於運行態,運行態最重要的是對系統的監控要準確及時,發現問題可以準確報警而且報警數據要準確詳細,以便於排查問題。
  6. 故障發生:故障發生時首先最重要的就是及時止損,例如因爲程序問題致使商品價格錯誤,那就要及時下架商品或者關閉購買連接,防止形成重大資產損失。而後要可以及時恢復服務,並定位緣由解決問題。

針對秒殺系統,咱們在遇到大流量時,應該從哪些方面來保障系統的穩定運行,更多的是看針對運行階段進行處理。

  • 降級,就是當系統的容量達到必定程度時,限制或者關閉系統的某些非核心功能,從而把有限的資源保留給更核心的業務。
  • 限流,就是限制一部分流量來保護系統,並作到既能夠人工執行開關,也支持自動化保護的措施。
  • 拒絕服務,當系統負載達到必定閾值時,例如CPU使用率達到90%或者系統load值達到2*CPU核數時,系統直接拒絕全部請求。能夠在nginx上設置過載保護,這是最後的兜底方案。
相關文章
相關標籤/搜索