說起秒殺,我想你肯定不陌生,這兩年,從雙十一購物到春節搶紅包,再到 12306 搶火車 票,「秒殺」的場景處處可見。簡單來說,秒殺就是在同一個時刻有大量的請求爭搶購買同一個 商品並完成交易的過程,用技術的行話來說就是大量的並發讀和並發寫。

不管是哪一門語言,並發都是程序員們最為頭疼的部分。同樣,對於一個軟體而言也是這樣,你 可以很快增刪改查做出一個秒殺系統,但是要讓它支持高並發訪問就沒那麼容易了。比如說,如 何讓系統面對百萬級的請求流量不出故障?如何保證高並發情況下數據的一致性寫?完全靠堆服 務器來解決嗎?這顯然不是最好的解決方案。

在我看來,秒殺系統本質上就是一個滿足大並發、高性能和高可用的分散式系統。今天,我們就 來聊聊,如何在滿足一個良好架構的分散式系統基礎上,針對秒殺這種業務做到極致的性能改 進。

架構原則:「4 要 1 不要」

如果你是一個架構師,你首先要勾勒出一個輪廓,想一想如何構建一個超大流量並發讀寫、高性 能,以及高可用的系統,這其中有哪些要素需要考慮。我把這些要素總結為「4 要 1 不要」。

1. 數據要盡量少

所謂「數據要盡量少」,首先是指用戶請求的數據能少就少。請求的數據包括上傳給系統的數據 和系統返回給用戶的數據(通常就是網頁)。

為啥「數據要盡量少」呢?因為首先這些數據在網路上傳輸需要時間,其次不管是請求數據還是 返回數據都需要伺服器做處理,而伺服器在寫網路時通常都要做壓縮和字元編碼,這些都非常消 耗 CPU,所以減少傳輸的數據量可以顯著減少 CPU 的使用。例如,我們可以簡化秒殺頁面的大 小,去掉不必要的頁面裝修效果,等等。

其次,「數據要盡量少」還要求系統依賴的數據能少就少,包括系統完成某些業務邏輯需要讀取 和保存的數據,這些數據一般是和後台服務以及資料庫打交道的。調用其他服務會涉及數據的序 列化和反序列化,而這也是 CPU 的一大殺手,同樣也會增加延時。而且,資料庫本身也容易成 為一個瓶頸,所以和資料庫打交道越少越好,數據越簡單、越小則越好。

2. 請求數要盡量少

用戶請求的頁面返回後,瀏覽器渲染這個頁面還要包含其他的額外請求,比如說,這個頁面依賴 的 CSS/JavaScript、圖片,以及 Ajax 請求等等都定義為「額外請求」,這些額外請求應該盡量 少。因為瀏覽器每發出一個請求都多少會有一些消耗,例如建立連接要做三次握手,有的時候有 頁面依賴或者連接數限制,一些請求(例如 JavaScript)還需要串列載入等。另外,如果不同請 求的域名不一樣的話,還涉及這些域名的 DNS 解析,可能會耗時更久。所以你要記住的是,減 少請求數可以顯著減少以上這些因素導致的資源消耗。

例如,減少請求數最常用的一個實踐就是合併 CSS 和 JavaScript 文件,把多個 JavaScript 文件 合併成一個文件,在 URL 中用逗號隔開(g.xxx.com/tm/xx-b/4.0.9 module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js)。這種 方式在服務端仍然是單個文件各自存放,只是服務端會有一個組件解析這個 URL,然後動態把這 些文件合併起來一起返回。

3. 路徑要盡量短

所謂「路徑」,就是用戶發出請求到返回數據這個過程中,需求經過的中間的節點數

通常,這些節點可以表示為一個系統或者一個新的 Socket 連接(比如代理伺服器只是創建一個 新的 Socket 連接來轉發請求)。每經過一個節點,一般都會產生一個新的 Socket 連接。

然而,每增加一個連接都會增加新的不確定性。從概率統計上來說,假如一次請求經過 5 個節 點,每個節點的可用性是 99.9% 的話,那麼整個請求的可用性是:99.9% 的 5 次方,約等於 99.5%。

所以縮短請求路徑不僅可以增加可用性,同樣可以有效提升性能(減少中間節點可以減少數據的 序列化與反序列化),並減少延時(可以減少網路傳輸耗時)。

要縮短訪問路徑有一種辦法,就是多個相互強依賴的應用合併部署在一起,把遠程過程調用 (RPC)變成 JVM 內部之間的方法調用。在《大型網站技術架構演進與性能優化》一書中,我 也有一章介紹了這種技術的詳細實現。

4. 依賴要盡量少

所謂依賴,指的是要完成一次用戶請求必須依賴的系統或者服務,這裡的依賴指的是強依賴

舉個例子,比如說你要展示秒殺頁面,而這個頁面必須強依賴商品信息、用戶信息,還有其他如 優惠券、成交列表等這些對秒殺不是非要不可的信息(弱依賴),這些弱依賴在緊急情況下就可 以去掉。

要減少依賴,我們可以給系統進行分級,比如 0 級系統、1 級系統、2 級系統、3 級系統,0 級 系統如果是最重要的系統,那麼 0 級系統強依賴的系統也同樣是最重要的系統,以此類推。 注意,0 級系統要盡量減少對 1 級系統的強依賴,防止重要的系統被不重要的系統拖垮。例如支 付系統是 0 級系統,而優惠券是 1 級系統的話,在極端情況下可以把優惠券給降級,防止支付系 統被優惠券這個 1 級系統給拖垮。

5. 不要有單點

系統中的單點可以說是系統架構上的一個大忌,因為單點意味著沒有備份,風險不可控,我們設 計分散式系統最重要的原則就是「消除單點」。

那如何避免單點呢?我認為關鍵點是避免將服務的狀態和機器綁定,即把服務無狀態化,這樣服 務就可以在機器中隨意移動。

如何那把服務的狀態和機器解耦呢?這裡也有很多實現方式。例如把和機器相關的配置動態化, 這些參數可以通過配置中心來動態推送,在服務啟動時動態拉取下來,我們在這些配置中心設置 一些規則來方便地改變這些映射關係。

應用無狀態化是有效避免單點的一種方式,但是像存儲服務本身很難無狀態化,因為數據要存儲 在磁碟上,本身就要和機器綁定,那麼這種場景一般要通過冗餘多個備份的方式來解決單點問 題。

前面介紹了這些設計上的一些原則,但是你有沒有發現,我一直說的是「盡量」而不是「絕 對」?

我想你肯定會問是不是請求最少就一定最好,我的答案是「不一定」。我們曾經把有些 CSS 內聯 進頁面里,這樣做可以減少依賴一個 CSS 的請求從而加快首頁的渲染,但是同樣也增大了頁面的 大小,又不符合「數據要盡量少」的原則,這種情況下我們為了提升首屏的渲染速度,只把首屏 的 HTML 依賴的 CSS 內聯進來,其他 CSS 仍然放到文件中作為依賴載入,盡量實現首屏的打開 速度與整個頁面載入性能的平衡

所以說,架構是一種平衡的藝術,而最好的架構一旦脫離了它所適應的場景,一切都將是空談。 我希望你記住的是,這裡所說的幾點都只是一個個方向,你應該盡量往這些方向上去努力,但也 要考慮平衡其他因素。

不同場景下的不同架構案例

前面我說了一些架構上的原則,那麼針對「秒殺」這個場景,怎樣才是一個好的架構呢?下面我 以淘寶早期秒殺系統架構的演進為主線,來幫你梳理不同的請求體量下,我認為的最佳秒殺系統 架構。

如果你想快速搭建一個簡單的秒殺系統,只需要把你的商品購買頁面增加一個「定時上架」功 能,僅在秒殺開始時才讓用戶看到購買按鈕,當商品的庫存賣完了也就結束了。這就是當時第一 個版本的秒殺系統實現方式

但隨著請求量的加大(比如從 1w/s 到了 10w/s 的量級),這個簡單的架構很快就遇到了瓶頸, 因此需要做架構改造來提升系統性能。這些架構改造包括:

1. 把秒殺系統獨立出來單獨打造一個系統,這樣可以有針對性地做優化,例如這個獨立出來的系 統就減少了店鋪裝修的功能,減少了頁面的複雜度; 2. 在系統部署上也獨立做一個機器集群,這樣秒殺的大流量就不會影響到正常的商品購買集群的 機器負載; 3. 將熱點數據(如庫存數據)單獨放到一個緩存系統中,以提高「讀性能」; 4. 增加秒殺答題,防止有秒殺器搶單。

此時的系統架構變成了下圖這個樣子。最重要的就是,秒殺詳情成為了一個獨立的新系統,另外 核心的一些數據放到了緩存(Cache)中,其他的關聯繫統也都以獨立集群的方式進行部署。

改造後的系統架構

然而這個架構仍然支持不了超過 100w/s 的請求量,所以為了進一步提升秒殺系統的性能,我們 又對架構做進一步升級,比如:

  1. 對頁面進行徹底的動靜分離,使得用戶秒殺時不需要刷新整個頁面,而只需要點擊搶寶按鈕, 藉此把頁面刷新的數據降到最少;
  2. 在服務端對秒殺商品進行本地緩存,不需要再調用依賴系統的後台服務獲取數據,甚至不需要 去公共的緩存集群中查詢數據,這樣不僅可以減少系統調用,而且能夠避免壓垮公共緩存集 群。
  3. 增加系統限流保護,防止最壞情況發生。

經過這些優化,系統架構變成了下圖中的樣子。在這裡,我們對頁面進行了進一步的靜態化,秒 殺過程中不需要刷新整個頁面,而只需要向服務端請求很少的動態數據。而且,最關鍵的詳情和 交易系統都增加了本地緩存,來提前緩存秒殺商品的信息,熱點資料庫也做了獨立部署,等等。

從前面的幾次升級來看,其實越到後面需要定製的地方越多,也就是越「不通用」。例如,把秒 殺商品緩存在每台機器的內存中,這種方式顯然不適合太多的商品同時進行秒殺的情況,因為單 機的內存始終有限。所以要取得極致的性能,就要在其他地方(比如,通用性、易用性、成本等 方面)有所犧牲。

總結

來讓我們回顧下前面的內容,我首先介紹了構建大並發、高性能、高可用系統中幾種通用的優化 思路,並抽象總結為「4 要 1 不要」原則,也就是:數據要盡量少、請求數要盡量少、路徑要盡 量短、依賴要盡量少,以及不要有單點。當然,這幾點是你要努力的方向,具體操作時還是要密 切結合實際的場景和具體條件來進行。

然後,我給出了實際構建秒殺系統時,根據不同級別的流量,由簡單到複雜打造的幾種系統架 構,希望能供你參考。當然,這裡面我沒有說具體的解決方案,比如緩存用什麼、頁面靜態化用 什麼,因為這些對於架構來說並不重要,作為架構師,你應該時刻提醒自己主線是什麼。

說了這麼多,總體上我希望給你一個方向,就是想構建大並發、高性能、高可用的系統應該從哪 幾個方向上去努力,然後在不同性能要求的情況下系統架構應該從哪幾個方面去做取捨。同時你 也要明白,越追求極致性能,系統定製開發就會越多,同時系統的通用性也就會越差。

工作一到五年的程序員朋友面對目前的技術無從下手,感到很迷茫,高清思維導圖及相關視頻資料獲取方式關注+轉發+私信【架構】裡面有阿里Java高級大牛直播講解知識點,分享知識,課程內容都是各位老師多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!


推薦閱讀:
相关文章