每日最新頭條.有趣資訊

每個架構師都應掌握的六大架構伸縮性原則

作者 | Ian Gorton

譯者 | 無名

編輯 | 小智

Pornhub 2019 年訪問量超 420 億,互聯網海量規模的網站架構背後,需要怎樣的可伸縮性?

我們生活在這樣的一個時代,像谷歌、亞馬遜、Facebook 這樣規模龐大的互聯網公司成為一種工程標誌。它們每秒處理大量的請求,管理著規模空前的數據庫存儲。由於商業機密方面的原因,要精確獲得這些系統的流量數據並不容易。但不管怎樣,隨著用戶的增長,伸縮能力是這些公司持續取得成功的關鍵因素。

所幸的是,我們可以通過一家公司的年度使用報告來深入了解互聯網規模的請求量和數據量。這是 2019 年以來極其詳細的使用統計數據:

https://www.pornhub.com/insights/2019-year-in-review

不過需要注意的是,這份報告針對的是 pornhub.com,不適合那些有神經質的人看。報告表明,2019 年 Pornhub 網站有 420 億的訪問量!

對於絕大多數商業和政府系統來說,在開發和部署的早期階段,可伸縮性並不是主要的需求,可用性和實用的新功能才是開發周期的驅動因素。只要能夠滿足正常的負載,就會不斷加入新功能來提高系統的業務價值。

但是,系統也會發展到性能和可伸縮性成為緊迫問題甚至是生存問題的階段,這種情況並不少見。功能方面取得的成功催生了更多與處理和數據管理相關的需求。這通常預示著一個轉折點,在輕負載下合理的設計決策現在突然變成了技術債務。外部觸發的事件往往會成為引爆點——看看 2020 年 3 月份媒體上有關失業以及超市網上訂購網站因冠狀病毒大流行而供不應求的大量報導。

當達到這個臨界點時,架構師就有責任將系統演化成一個響應迅速、可伸縮的系統。核心系統架構機制和模式需要進行重新設計,以便提升處理能力。對於很多架構師來說,這是一個未知或不太熟悉的領域,因為可伸縮性問題有時候會把我們引向一條與一般性軟體架構原則不太一樣的道路。

下面的六條經驗法則是每個軟體架構師在構建可伸縮系統時都應該掌握的知識。這些通用規則可以作為架構師指南,應對不斷增長的負載和數據量處理需求。

1

成本和伸縮性之間的關係

對系統進行伸縮的一個核心原則是能夠方便地添加新資源來處理增長的負載。對於很多系統來說,一個簡單而有效的方法是部署多個無狀態伺服器實例,並使用負載均衡器在這些實例之間分配請求 (見圖 1)。假設這些資源部署在雲平台上,比如 Amazon Web Services,那麽成本就是:

每個伺服器實例的虛擬機部署成本。

負載均衡器的成本,由新請求和活躍請求的數量以及所處理的數據量決定。

在這個場景中,隨著請求負載的增長,已部署的虛擬機需要擁有更大的處理能力,這導致了更高的成本。負載均衡器的開銷也會隨著請求負載和數據大小成比例增長。

圖 1:簡單的負載均衡器示例

因此,成本和規模是相輔相成的。可伸縮性的設計決策不可避免地會影響部署成本。如果忽略了這一點,你可能會在月底收到意想不到的巨額部署账單!

那麽該如何降低成本?主要有兩種方式:

使用彈性負載均衡器,根據實時請求負載來調整伺服器實例的數量。在流量較小的時候,只需要為一部分伺服器實例付費。隨著請求量的增長,負載均衡器會產生新的實例,容量也會相應地增長。

增加每個伺服器實例的容量。這通常是通過調優伺服器部署參數 (例如線程數、連接數、堆大小等) 來實現。仔細選擇參數設置可以顯著提高性能,從而提高容量。你基本上是用相同的資源做了更多的工作——這是實現伸縮性的一個關鍵原則。

2

注意系統瓶頸

對一個系統進行伸縮本質上就是要增加它的容量。在上面的示例中,我們通過部署更多的伺服器實例來提高請求處理能力。但是,軟體系統是由多個相互依賴的處理元素或微服務組成的,所以在增加一部分微服務容量的同時,不可避免地會被其他一些微服務拖累。

在我們的負載均衡示例中,假設我們的伺服器實例都連接到同一個共享數據庫。隨著部署伺服器數量的增加,數據庫的請求負載也隨之增加 (參見圖 2)。在某個階段,數據庫將達到飽和,數據庫訪問將開始出現更大的延遲。現在,數據庫成為瓶頸——即使你增加更多的伺服器處理能力也無濟於事。如果要進一步伸縮,就需要以某種方式增加數據庫的容量。你可以嘗試優化查詢,或者增加更多的 CPU 或記憶體。你也可以對數據庫進行複製或分片。除了這些,還有其他很多解決方案。

圖 2:增加服務能力導致數據庫成為瓶頸

系統中的共享資源都可能成為瓶頸。當你在架構中的某些部分增加容量時,需要仔細考慮下遊的容量,確保不會突然給系統造成衝擊。因為這樣會迅速導致級聯故障 (參見下一條規則),並導致整個系統崩潰。

數據庫、消息隊列、長延遲網絡連接、線程和連接池以及共享微服務都是潛在的瓶頸。可以肯定的是,高流量負載會很快讓這些瓶頸暴露出來。關鍵的是要能夠在瓶頸暴露出來的時候能夠防止系統突然崩潰,並快速部署更多的能力。

3

慢服務比故障服務更有害

在正常情況下下,系統應該能夠為微服務和數據庫提供穩定、低延遲的通信。當系統負載保持在正常的配置水準時,性能是可預測、一致和快速的,如圖 3 所示。

圖 3:正常負載下的低延遲

當客戶端負載超過正常水準時,微服務之間的請求延遲將開始增加。首先,這通常是一種緩慢但穩定的增長,不會嚴重影響整個系統的運行,特別是如果負載激增是短暫的。但是,如果傳入的請求負載繼續超過容量 (服務 B),未完成的請求將在微服務 A 中堆積,由於下遊延遲變慢,該微服務現在接收的請求比已完成的請求還要多。如圖 4 所示。

圖 4:負載增加導致延遲增加、請求堆積

在這種情況下,事情可能很快就會惡化。當一個服務由於抖動或資源耗盡而不堪重負,服務就會無法響應客戶端,客戶端也將陷入停滯。直接導致的結果就是級聯故障——慢服務會導致請求沿著請求路徑不斷累積,直到整個系統崩潰。

這就是為什麽說慢服務比不可用的服務更有害。如果你調用了一個失敗的服務或一個由於臨時網絡問題而出現分區的服務,你會立即收到一個異常,並且可以決定該怎麽處理 (例如回退和重試、報告錯誤)。漸進式超負荷的服務能夠正常運行,只是延遲變長了,而這暴露了所有依賴服務的潛在瓶頸,最終會導致嚴重的錯誤。

一些架構模式(如回路斷路器和隔板)可用於防止級聯故障。如果服務的延遲超過指定值,斷路器就會調節請求負載,甚至是將其斷開。當只有一個下遊依賴項發生故障時,隔板可以保護上遊的微服務不發生故障。這些措施可用來構建彈性和高度可伸縮的架構。

4

數據層最難伸縮

數據庫實際上是每個系統的核心。它們通常包括“事實來源”事務數據庫,包含了業務正常運行所需的核心數據和向數據倉庫提供臨時數據項的運營數據源。

核心業務數據包括客戶概況、交易和帳戶餘額,它們必須是正確、一致且可用的。

運營數據包括用戶會話長短、每小時的訪問者和頁面瀏覽計數。這些數據通常有一個保存期限,可以基於時間段進行聚合和匯總。如果不是百分百完整問題也並不大。因此,我們可以更容易地捕獲和存儲運營數據,例如將其寫入到日誌文件或消息隊列。然後,使用者定期檢索數據並將其寫入到數據存儲。

隨著系統請求處理層的伸縮,共享事務數據庫的負載會逐漸增加。隨著查詢負載的增長,它們迅速成為瓶頸。查詢優化變得非常有用,同樣,也需要添加更多的記憶體,讓數據庫引擎能夠緩存索引和表數據。但最終數據庫引擎都會耗盡資源,需要進行更徹底的改變。

首先要注意的是,在數據層做出數據結構變更是件痛苦的事情。如果修改了關係型數據庫的模式,可能需要運行腳本重新加載數據來匹配新模式。在腳本運行期間 (對於大型數據庫來說,這可能是很長的一段時間),系統就不能執行寫操作。這可能會讓客戶不高興。

NoSQL、無模式的數據庫降低了對重新加載數據庫的需求,但仍然需要修改查詢代碼來匹配修改後的數據結構。如果你的業務數據中有一些數據項的格式已被修改,而有些保留原始格式,那麽你可能還需要進行數據對象版本控制。

進一步伸縮可能需要使用分布式數據庫,或許包含隻讀副本的首領和追隨者模型就足夠了。大多數數據庫都很容易配置這種模式,但需要進行密切的監控。當首領發生宕機,故障轉移需要花費一些時間,有時候還需要手動乾預。這些問題都非常依賴數據庫引擎。

如果採用無首領模式,則必須做好跨節點的數據分布和分區。在大多數數據庫中,一旦選擇了分區鍵,要做出變更就必須進行數據庫重建。所以,分區鍵的選擇要十分明智!分區鍵的選擇決定了數據是如何跨節點分布的。在添加節點時需要進行再均衡,以便在新節點之間傳播數據和請求。一般的數據庫文檔都描述了其工作原理,但有時這並不像想象得那麽容易。

由於管理分布式數據庫存在潛在的問題,基於雲的託管替代方案 (例如 AWS Dynamodb、Google Firestore) 往往是更受歡迎的選擇。當然,這也是要權衡利弊的。

通過修改邏輯和物理數據模型來擴展查詢處理能力通常不是一個平穩而簡單的過程,我們應該盡可能減少面對這樣的問題。

5

緩存、緩存、再緩存

降低數據庫負載的一種方法是盡可能避免經常訪問數據庫,而這就是緩存的用武之地。好的數據庫引擎應該能夠盡可能多地利用節點緩存。這是一個簡單而有用的解決方案,但成本可能很高。

如果不是必需的,為什麽一定要查詢數據庫呢?對於經常讀取和很少發生變化的數據,可以先嘗試從分布式緩存中獲取,比如 memcached。這需要進行遠程調用,但如果你需要的數據剛好存在於高速網絡的緩存中,這也比查詢數據庫實例快得多。

在引入緩存層後,我們需要修改處理邏輯,先從緩存中獲取數據。如果你想要的內容不在緩存中,就要查詢數據庫,然後將結果放到緩存中,並將其返回給調用者。你還需要決定何時刪除或讓緩存失效——這取決於應用程序對陳舊數據的容錯程度。

在伸縮系統時,設計良好的緩存方案絕對是無價的。如果你可以通過緩存處理很大比例的讀取請求,那麽就可以購買額外的數據庫容量,因為它們不需要處理大多數請求。這意味著在為越來越多的請求增加容量時,可以避免複雜而痛苦的數據層修改。這是一個讓每個人都開心的秘訣,即使是會計師。

6

監控是可伸縮系統的基礎

所有的團隊在面對大工作負載時都需要解決的一個問題是進行大規模測試。真實的負載測試很難進行。假設你想測試一個已有的部署,看看如果數據庫大小增加 10 倍之後是否仍然能夠提供快速的響應。你首先需要生成大量的數據,這些數據最好與實際的數據集和數據關係特徵相呼應。你還需要生成一個真實的工作負載。是用於讀取,還是用於讀和寫?然後你再加載和部署數據集,並進行負載測試,這可能需要使用負載測試工具。

這裡有很多工作要做。想要讓每一件事都接近真實是很難的,所以很少會有人這樣做。

另一種選擇是進行監控。簡單的系統監控包括監控基礎設施。如果資源耗盡,例如記憶體或磁盤空間不足或者遠程調用失敗,你都應該收到報警,以便在糟糕的事情發生之前采取補救措施。

監控是必要的,但還不夠。隨著系統的伸縮,你需要了解應用程序行為之間的關係。例如,當並發寫請求量增長時,數據庫寫操作是如何執行的。你還需要知道什麽時候回路斷路器會由於下遊延遲增加而斷開微服務連接,什麽時候負載均衡器開始生成新實例,或者消息在隊列中停留的時間是否超過了指定的閾值。

監控解決方案有很多。Splunk 是一個全面而強大的日誌聚合框架。雲平台都提供了自己的監控框架,比如 AWS Cloudwatch。用戶可以捕獲有關係統行為的指標,並在儀表盤中顯示這些指標,以便對性能進行監控和分析。“可觀察性”通常是指性能監控和分析的整個過程。

有兩個方面需要考慮到。

首先,為了深入了解性能,你需要生成與應用程序行為細節相關的自定義指標。仔細設計這些指標,並在微服務中加入代碼,將它們注入到監控框架中,以便在系統儀表盤中觀察和分析它們。

其次,監控是系統的必要功能 (和成本)。當你需要調優性能和伸縮系統時,你所捕獲的數據將會為你的實驗和工作提供指導。在系統演進過程中,基於數據驅動的方法有助於確保你的時間被用在修改和改進有用的事情上,而這些是系統性能和伸縮需求的基礎。

7

結論

對於大多數系統來說,高性能和可伸縮性通常不是優先考慮的需求。理解、實現和演進功能需求通常是很有問題的,會消耗掉所有可用的時間和預算。但是,有時候由於外部事件或意外事件的驅動,系統需要具備可伸縮性,否則系統就變得不可用,因為它可能在高負載下發生崩潰。不可用的系統 (或由於性能差導致可用性很差的系統) 對任何人來說都是沒有用處的。

就像任何一種複雜的軟體架構一樣,解決系統伸縮性問題也並不存在什麽銀彈。以精確的系統需求作為指引,做出權衡和妥協對於實現可伸縮性來說至關重要。記住上述的這些規則,通向可伸縮性的道路上就會少踩一些意想不到的坑!

8

小彩蛋

以上是架構伸縮性原則的原理解析,配合下篇實踐內容閱讀效果更佳:

參考閱讀:

https://medium.com/@i.gorton/six-rules-of-thumb-for-scaling-software-architectures-a831960414f9

InfoQ 寫作平台歡迎所有熱愛技術、熱愛創作、熱愛分享的內容創作者入駐!

還有更多超值活動等你來!

獲得更多的PTT最新消息
按讚加入粉絲團