1. 程式人生 > >如何設計一個秒殺系統——秒殺系統架構設計有哪些關鍵點?(持續更新)

如何設計一個秒殺系統——秒殺系統架構設計有哪些關鍵點?(持續更新)

最近在閱讀許令波的極客專欄“如何設計一個秒殺系統”,我希望通過做筆記的方式督促自己認真閱讀學到東西,在此同時也能分享一些我從中得到的一些東西。

話說雙11,秒殺系統的知識也能應用到裡面,在平時架構設計中也可運用這篇專欄的知識,這裡有些知識點也是面試的高頻詞,也可以完善對於整個架構的認識。

我這裡只是做一些總結,有興趣的前往極客專欄訂閱。

本文持續更新,直到閱讀完此專欄~~~

許令波簡介:

許令波,花名“君山”。說起來我的職業生涯算是比較簡單,2009 年大學畢業後就進入了淘寶,一直工作了七年多。這七年多的時間裡,我有幸看到了淘寶業務的快速增長,並且以開發者的身份參與其中。
說實話,作為一名程式設計師,我的技術能力也在公司業務的快速增長過程中得到了歷練,並積累了一些大流量高併發網站架構設計和優化的經驗,尤其是針對“秒殺”這個場景。因為我確信,那個時候我們肯定是對系統做了足夠多的極致優化,才能扛住當時洪峰般的流量請求。

目錄

開篇詞 | 秒殺系統架構設計都有哪些關鍵點?

如何理解秒殺系統?

作為程式設計師,從整體思考問題,主要解決兩個問題:
1.併發讀 核心:減少使用者到服務端讀資料,或讀更少的資料
2.併發寫 核心:處理原則也一樣,要求我們在資料庫層面獨立一個庫,特殊處理,還要設計兜底方案,防止最壞情況發生。

從架構師角度考慮: 如何打造並維護一個超大流量併發讀寫、高效能、高可用系統。
具體分為三塊:
1.高效能

支援高併發訪問,主要四個方面來做優化
(1)資料庫的動靜分離方案 (2)熱點的發現與隔離 (3)請求的削峰與分層過濾 (4)服務端的極致優化


2.一致性

秒殺商品的減庫存,保證資料的一致性。有限的商品會在某一時刻被大量請求同時減庫存,並且減庫存的方案也有多種。


3.高可用

保證系統的高可用和確定性,還要準備一個planB  

01 | 設計秒殺系統時應該注意的5個架構原則——筆記總結第1篇

這一篇主要涉及秒殺系統的5個架構原則,首先架構一定要有對應的場景,否則就是胡說八道,而架構也是一種平衡的藝術,你就像指揮官,由你來決定哪些資源投入戰場。

具體到秒殺的場景,我們就需要做到快速增刪查改的系統,並且還要支援百萬級的請求流量而不出故障,以及高併發情況下資料的一致性。光靠堆伺服器是不可能實現的。秒殺系統本質上是一個滿足大併發,高效能和高可用的分散式系統。

那麼作為一個架構師,需要考慮哪些因素呢?

架構原則:“4要1不要”

1.資料儘量少

(1)請求的資料要少

 包括上傳給系統的資料和系統返回給使用者的資料(也就是網頁)。因為請求資料和返回資料都是要消耗伺服器資源的,而伺服器在寫網路時通常還要壓縮和字元編碼,會消耗cup資源。我們可以減少秒殺頁面的大小,去掉不必要的裝飾……。

(2)系統依賴的資料要少

包括系統需要儲存和讀取的資料,這些資料在後臺服務和資料庫走動。呼叫不同服務的資料,需要序列化與反序列化,也是很耗費系統資源。並且資料庫本事也是有侷限的。

2.請求數儘量要少

當請求的頁面返回時,還需要其他額外的請求,比如css,js,圖片,ajax請求,建立連線需要三次握手,一些請求需要序列化載入,如果不同請求的域名不同,還需要DNS解析,會更耗時,所以減少請求數吧。

實踐:合併css與js檔案。

3.依賴儘量要少

一次使用者請求必須依賴的系統或者服務,比如秒殺頁面需要強依賴商品資訊,使用者資訊等,弱依賴如優惠券,成交列表。

可以對系統進行分級,0級系統減少對1級系統的依賴,如支付系統不要被優惠券給拖垮了。

4.路徑儘量要短

請求到返回資料的過程中,經過的中間節點數。節點表示為一個系統或者一個新的Socket連線。

縮短路徑不僅可以增加可用性,通用提升效能(減少序列與反序列化),減少耗時(網路傳輸)

5.不要有單點

單點意味沒有備份,風險不可靠。

(1)避免服務的狀態和機器繫結,即服務無狀態化。

(2)應用無狀態化。

上一篇文章講到,架構就是平衡的藝術,如果一味的把css內聯進頁面,雖然減少了一個css請求,加快了頁面渲染,但是同樣也增加了頁面的大小,又不符合“資料要儘量少”的原則,這種情況下我們為了提升首屏的渲染速度,只把首屏的 HTML 依賴的 CSS 內聯進來,其他 CSS 仍然放到檔案中作為依賴載入,儘量實現首屏的開啟速度與整個頁面載入效能的平衡。

秒殺架構改造方案(1w/s-10w/s):

(1)把秒殺系統獨立成一個系統,針對性優化。

(2)系統部署成單獨的機器叢集

(3)熱點資料放入一個快取系統

(4)增加秒殺答題,防止秒殺器搶單

100w/s

(1)頁面徹底的動靜分離

(2)秒殺商品進行本地快取,這樣可以減少系統呼叫,減少公共快取叢集壓力。

(3)增加系統限流保護

隨著對秒殺系統的特色化定製越多,它的通用性就越低,處處都體現架構的動態平衡,有舍有得啊,說不定等成了架構師,哲學都入門到精通了。

02 | 如何做好資料的動靜分離,關鍵點與方案——筆記總結第2篇

靜態資料是什麼

輸出資料是否含有和URL、瀏覽者、時間、地域相關,以及是否含有cookie等私密資料

比如:任何人訪問的頁面的都是一樣的,則是典型的靜態資料

而想頭條的新聞的就屬於動態的,每個人都不一樣。

靜態並不是真正意義上的HTML頁面,而是對於訪問者是否有定製化的資料。

為什麼

為了讓系統快起來有兩個方法1.提高單次請求的效率 2.減少沒必要的請求

資料的動靜分離就是這個方向。而把靜態資料快取了之後,訪問效率就提高了。

怎麼做

靜態資料的做快取的幾個關鍵點:

1.把資料快取在離使用者最近的地方

使用者瀏覽器中,CDN,伺服器Cache中

2.靜態化改造就是要直接快取HTTP連線而不是僅僅快取資料

如下圖所示,Web 代理伺服器根據請求 URL,直接取出對應的 HTTP 響應頭和響應體然後直接返回,這個響應過程簡單得連 HTTP 協議都不用重新組裝,甚至連 HTTP 請求頭也不需要解析。

3.用什麼來快取,不同的語言特點不同,java並不擅長處理大量請求連線,servlet容器解析http協議較慢,我們可以在web伺服器層上做,如Ngnix、Apache、Varnish

以商品詳情繫統為例作分析

1.URL唯一化,快取整個http,以URL為key

2.分離時間因素

服務端輸出的時間也通過動態請求獲取

3.非同步化地域因素

詳情上的地域相關因素可用非同步獲取,當然可以做成動態請求

4.分離瀏覽者相關的因素。

如登入,登入身份單獨拆開,通過動態請求獲取。

5.去掉cookie

服務端輸出的頁面包含的 Cookie 可以通過程式碼軟體來刪除,如 Web 伺服器 Varnish 可以通過 unset req.http.cookie 命令去掉 Cookie。注意,這裡說的去掉 Cookie 並不是使用者端收到的頁面就不含 Cookie 了,而是說,在快取的靜態資料中不含有 Cookie。

分離出動態資料後,將資訊json化,方便前端獲取

快取處理靜態資料,而動臺數據的處理一般有兩種方案

1.ESL方案(或SSI)

在WEB代理伺服器做動態內容請求,再將請求插入到靜態頁面,使用者拿到頁面就是完整的,體驗好。

2.CSI方案

單獨傳送js請求,獲取動態內容,但是使用者端頁面,可能會延時,體驗差。

動靜分離的的幾種架構方案

1.實體機單機部署

這種方案是將虛擬機器改為實體機,以增大 Cache 的容量,並且採用了一致性 Hash 分組的方式來提升命中率。這裡將 Cache 分成若干組,是希望能達到命中率和訪問熱點的平衡。Hash 分組越少,快取的命中率肯定就會越高,但短板是也會使單個商品集中在一個分組中,容易導致 Cache 被擊穿,所以我們應該適當增加多個相同的分組,來平衡訪問熱點和命中率的問題。


圖 2 Nginx+Cache+Java 結構實體機單機部署
實體機單機部署有以下幾個優點:

沒有網路瓶頸,而且能使用大記憶體;
既能提升命中率,又能減少 Gzip 壓縮;
減少 Cache 失效壓力,因為採用定時失效方式,例如只快取 3 秒鐘,過期即自動失效。
這個方案中,雖然把通常只需要虛擬機器或者容器執行的 Java 應用換成實體機,優勢很明顯,它會增加單機的記憶體容量,但是一定程度上也造成了 CPU 的浪費,因為單個的 Java程序很難用完整的實體機CPU。

另外就是,單個實體機上部署了 Java 應用又作為 Cache 來使用,這造成了運維上的高複雜度,所以這是一個折中的方案。如果你的公司裡,沒有更多的系統有類似需求,那麼這樣做也比較合適,如果你們有多個業務系統都有靜態化改造的需求,那還是建議把 Cache 層單獨抽出來公用比較合理,如下面的方案 2 所示。

方案 2:統一 Cache 層

所謂統一 Cache 層,就是將單機的 Cache 統一分離出來,形成一個單獨的 Cache 叢集。統一 Cache 層是個更理想的可推廣方案

將 Cache 層單獨拿出來統一管理可以減少運維成本,同時也方便接入其他靜態化系統。此外,它還有一些優點。

單獨一個 Cache 層,可以減少多個應用接入時使用 Cache 的成本。這樣接入的應用只要維護自己的 Java 系統就好,不需要單獨維護 Cache,而只關心如何使用即可。
統一 Cache 的方案更易於維護,如後面加強監控、配置的自動化,只需要一套解決方案就行,統一起來維護升級也比較方便。
可以共享記憶體,最大化利用記憶體,不同系統之間的記憶體可以動態切換,從而能夠有效應對各種攻擊。
這種方案雖然維護上更方便了,但是也帶來了其他一些問題,比如快取更加集中,導致:

Cache 層內部交換網路成為瓶頸;
快取伺服器的網絡卡也會是瓶頸;
機器少風險較大,掛掉一臺就會影響很大一部分快取資料。
要解決上面這些問題,可以再對 Cache 做 Hash 分組,即一組 Cache 快取的內容相同,這樣能夠避免熱點資料過度集中導致新的瓶頸產生。

方案 3:上 CDN

在將整個系統做動靜分離後,我們自然會想到更進一步的方案,就是將 Cache 進一步前移到 CDN 上,因為 CDN 離使用者最近,效果會更好。

但是要想這麼做,有以下幾個問題需要解決。

失效問題。前面我們也有提到過快取時效的問題,不知道你有沒有理解,我再來解釋一下。談到靜態資料時,我說過一個關鍵詞叫“相對不變”,它的言外之意是“可能會變化”。比如一篇文章,現在不變,但如果你發現個錯別字,是不是就會變化了?如果你的快取時效很長,那使用者端在很長一段時間內看到的都是錯的。所以,這個方案中也是,我們需要保證 CDN 可以在秒級時間內,讓分佈在全國各地的 Cache 同時失效,這對 CDN 的失效系統要求很高。
命中率問題。Cache 最重要的一個衡量指標就是“高命中率”,不然 Cache 的存在就失去了意義。同樣,如果將資料全部放到全國的 CDN 上,必然導致 Cache 分散,而 Cache 分散又會導致訪問請求命中同一個 Cache 的可能性降低,那麼命中率就成為一個問題。
釋出更新問題。如果一個業務系統每週都有日常業務需要釋出,那麼釋出系統必須足夠簡潔高效,而且你還要考慮有問題時快速回滾和排查問題的簡便性。
從前面的分析來看,將商品詳情繫統放到全國的所有 CDN 節點上是不太現實的,因為存在失效問題、命中率問題以及系統的釋出更新問題。那麼是否可以選擇若干個節點來嘗試實施呢?答案是“可以”,但是這樣的節點需要滿足幾個條件:

靠近訪問量比較集中的地區;
離主站相對較遠;
節點到主站間的網路比較好,而且穩定;
節點容量比較大,不會佔用其他 CDN太多請求

最後,還有一點也很重要,那就是:節點不要太多。

基於上面幾個因素,選擇 CDN 的二級 Cache 比較合適,因為二級 Cache 數量偏少,容量也更大,讓使用者的請求先回源的 CDN 的二級 Cache 中,如果沒命中再回源站獲取資料,

3.上CDN

使用 CDN 的二級 Cache 作為快取,可以達到和當前服務端靜態化 Cache 類似的命中率,因為節點數不多,Cache 不是很分散,訪問量也比較集中,這樣也就解決了命中率問題,同時能夠給使用者最好的訪問體驗,是當前比較理想的一種 CDN 化方案。

除此之外,CDN 化部署方案還有以下幾個特點:

把整個頁面快取在使用者瀏覽器中;
如果強制重新整理整個頁面,也會請求 CDN;
實際有效請求,只是使用者對“重新整理搶寶”按鈕的點選。
這樣就把 90% 的靜態資料快取在了使用者端或者 CDN 上,當真正秒殺時,使用者只需要點選特殊的“重新整理搶寶”按鈕,而不需要重新整理整個頁面。這樣一來,系統只是向服務端請求很少的有效資料,而不需要重複請求大量的靜態資料。

秒殺的動態資料和普通詳情頁面的動態資料相比更少,效能也提升了 3 倍以上。所以“搶寶”這種設計思路,讓我們不用重新整理頁面就能夠很好地請求到服務端最新的動態資料。

總結一下
今天主要講了實現動靜分離的幾種思路,由易到難給出了幾種架構方案,以及它們各自的優缺點。不同的架構方案會引入不同的問題,比如我們把快取資料從 CDN 上移到使用者的瀏覽器裡,針對秒殺這個場景是沒問題的,但針對一般的商品可就不一定了。

儲存在瀏覽器或 CDN 上,有多大區別?

區別很大!因為在 CDN 上,我們可以做主動失效,而在使用者的瀏覽器裡就更不可控,如果使用者不主動重新整理的話,你很難主動地把訊息推送給使用者的瀏覽器。

另外,在什麼地方把靜態資料和動態資料合併並渲染出一個完整的頁面也很關鍵,假如在使用者的瀏覽器裡合併,那麼服務端可以減少渲染整個頁面的 CPU 消耗。如果在服務端合併的話,就要考慮快取的資料是否進行 Gzip 壓縮了:如果快取 Gzip 壓縮後的靜態資料可以減少快取的資料量,但是進行頁面合併渲染時就要先解壓,然後再壓縮完整的頁面資料輸出給使用者;如果快取未壓縮的靜態資料,這樣不用解壓靜態資料,但是會增加快取容量。雖然這些都是細節問題,但你在設計架構方案時都需要考慮清楚。