聊聊軟體架構伸縮性的三大法則

對於大多數商業和政府系統而言,在開發與部署的初期,可擴展性並非首要需求,而可用性以及實用的新功能才是開發週期的主要推動因素。只要係統能滿足正常負載,便會持續增加新功能以提升系統的業務價值。然而,當系統發展到某個階段時,效能和可擴展性會成為緊迫問題,甚至關乎系統的生存。在這種情況下,架構師有責任將系統演變為一個反應迅速且可伸縮的系統。

成本和伸縮性之間的關係

系統伸縮的核心原則之一是能夠方便地增添新資源以應對成長的負載。就許多系統來說,一種簡單且有效的方法是部署多個無狀態伺服器實例,並利用負載平衡器在這些實例之間分配請求,如下圖所示。

               

倘若這些資源部署在雲端平台上,其成本包括:每個伺服器執行個體的虛擬機器部署成本,以及由新請求和活躍請求的數量、所處理的資料量決定的負載平衡器成本。

在這個場景中,隨著請求負載的增加,已部署的虛擬機器需具備更大的處理能力,這便導致了更高的成本。同時,負載平衡器的開銷也會隨著請求負載和資料大小成比例成長。所以,成本與規模相互關聯,可擴展性的設計決策必然會影響部署成本。若忽略這一點,可能會收到意想不到的巨額部署帳單。

那麼,該如何降低成本呢?主要有兩種方式。其一,使用彈性負載平衡器,依據即時請求負載來調整伺服器實例的數量。其二,增加每個伺服器實例的容量,這通常是透過調整伺服器部署參數(如執行緒數、連線數、堆大小等)來實現。精心選擇參數設定能夠顯著提升效能,進而提高容量。

注意系統瓶頸

對一個系統進行伸縮,本質上是要提升它的容量。在上述範例中,我們透過部署更多的伺服器實例來增強請求處理能力。然而,軟體系統是由多個相互依存的處理元素或微服務構成的,所以在增加一部分微服務容量時,不可避免地會受到其他一些微服務的牽制。在我們的負載平衡範例裡,假設伺服器執行個體都連接至同一個共享資料庫。隨著部署的伺服器數量增多,資料庫的請求負載也隨之增加(如下圖)。


在某個階段,資料庫會達到飽和狀態,資料庫存取將開始出現較大的延遲。此時,資料庫成為瓶頸—— 即便增加更多的伺服器處理能力也於事無補。若要進一步進行伸縮,就需要以某種方式增加資料庫的容量。可以嘗試最佳化查詢,或增添更多的CPU 或內存,也可以對資料庫進行複製或分片。除此之外,還有許多其他解決方案。系統中的共享資源都有可能成為瓶頸。

當在架構的某些部分增加容量時,需要仔細考慮下游的容量,確保不會突然對系統造成衝擊。因為這樣會迅速引發級聯故障(請參閱下一條規則),並致使整個系統崩潰。資料庫、訊息佇列、長延遲網路連線、執行緒和連線池以及共享微服務都是潛在的瓶頸。可以肯定的是,高流量負載很快就會使這些瓶頸顯露出來。關鍵在於當瓶頸暴露時,能夠防止系統突然崩潰,並迅速部署更多的能力。

慢服務比故障服務有害

在正常情況下,系統應能夠為微服務和資料庫提供穩定且低延遲的通訊。當系統負載處於正常的配置水準時,效能是可預測、一致且快速的,如下圖所示。

                           

一旦客戶端負載超出正常水平,微服務之間的請求延遲就會開始增加。特別是在傳入的請求負載持續超過容量(如服務B)的情況下,未完成的請求會在微服務A 中堆積,由於下游延遲變慢,該微服務此時接收的請求比已完成的請求更多。

                         

當一個服務因抖動或資源耗盡而不堪重負時,服務將無法回應客戶端,客戶端也會陷入停滯狀態。其直接結果就是級聯故障- 緩慢的服務會致使請求沿著請求路徑不斷累積,直到整個系統崩潰。一些架構模式(如迴路斷路器和隔板)可用來防止級聯故障。若服務的延遲超過指定值,斷路器會調節請求負載,甚至將其斷開。當僅有一個下游相依性發生故障時,隔板能夠保護上游的微服務不發生故障。這些措施可用於建構具有彈性且高度可伸縮的架構。