亞馬遜的實踐:分散式系統的困難點

問題一:異構系統的不標準問題

在軟體開發和維運過程中,存在許多標準化問題。例如,軟體和應用缺乏統一的標準,通訊協議和資料格式各不相同,開發和維運的過程和方法也不一致。不同的軟體和程式語言帶來了不同的相容性問題,以及各自不同的開發、測試、維運標準。這種差異導致我們在開發和維運時採用不同的方法,從而增加了架構的複雜度。

舉例來說,有的軟體需要透過修改.conf 檔案來更改配置,而有些則需要呼叫管理API 介面。在通訊方面,不同軟體採用不同的協議,即使在相同的網路協議中,也可能出現不同的資料格式。不同的技術團隊由於使用了不同的技術堆疊,也會導致開發和維運方式的多樣化。這些差異都會使我們的分散式系統架構變得極為複雜。因此,分散式系統架構需要製定相應的規範。

例如,我注意到許多服務的API 返回錯誤時不使用HTTP 的錯誤狀態碼,而是返回HTTP 200,然後在響應體(HTTP Body)中的JSON 字串中包含類似“error, bla bla error message”的錯誤訊息。這種設計不僅反直覺,而且極大地影響了監控的實現。實際上,現在應該使用Swagger 等規範來標準化API。

另一個例子是組態管理。許多公司在軟體設定管理中只是採用簡單的key-value 形式,這種彈性雖然很高,但也容易被濫用。例如,不規範的配置命名,不規範的值,甚至在配置中直接嵌入前端展示內容。一個合理的配置管理應分為三層:底層和作業系統相關,中間層和中介軟體相關,最上層則是業務應用相關。底層和中間層的配置不應讓使用者隨意修改,而是透過模板選擇。例如,作業系統相關的配置應形成標準範本供使用者選擇,而非隨意修改。只有當配置管理形成了規範,我們才能有效控制各種系統的複雜性。

再例如,資料通訊協定通常包括協定頭和協定體。協議頭定義了基本的協定數據,而協議體則包含業務數據。我們需要在協議頭的定義上建立嚴格的標準,以便所有使用協議的團隊都遵循同一套規則。這種標準化能夠幫助我們更好地進行請求監控、調度和管理。

透過這些規範化措施,我們可以更好地控制分散式系統的複雜度,並提高系統的可維護性和穩定性。

問題二:系統架構中的服務依賴問題

在傳統的單體應用程式中,如果某台伺服器宕機,整個應用程式就會停止運作。然而,這並不意味著在分散式架構下就不會出現類似的問題。在分散式系統中,各個服務之間通常存在依賴關係。當依賴鏈上的某項服務發生故障時,可能會引發「骨牌」效應,導致一系列連鎖反應。因此,在分散式架構中,服務的依賴關係也可能帶來許多問題。

一個典型的情況是,非關鍵業務被關鍵業務依賴,結果導致非關鍵業務變得像關鍵業務一樣重要。服務依賴鏈中常會出現“木桶效應”,即整個系統的服務等級協定(SLA)由表現最差的那個服務決定。這屬於服務治理的範疇。有效的服務治理不僅要求我們定義每個服務的重要程度,還需要清楚地定義和描述關鍵業務的主要呼叫路徑。沒有這樣的治理和管理措施,我們將難以有效地運作和管理整個系統。

需要特別注意的是,儘管許多分散式架構在應用層面做到了業務隔離,但在資料庫層面卻沒有。如果一個非關鍵業務因為資料庫問題而導致整個資料庫負載過高,可能會拖垮整個系統,導致全站不可用。因此,在資料庫層面也需要進行相應的隔離。最佳實務是每條業務線使用獨立的資料庫。這也是亞馬遜伺服器的實踐之一:系統之間禁止直接存取對方的資料庫,只能透過服務介面進行互動。這種做法符合微服務架構的要求。

總而言之,我們不僅要拆分服務,還需要為每個服務分配獨立的資料庫,以避免不同服務之間相互幹擾。這樣才能真正實現業務隔離,提升系統的可靠性與穩定性。

問題三:故障發生的機率較大

在分散式系統中,由於使用的機器和服務數量龐大,故障發生的機率比傳統的單體應用更高。雖然分散式系統可以透過隔離來減少故障的影響範圍,但由於組件繁多且結構複雜,故障的發生頻率反而更高。另一方面,由於管理難度大,系統架構全貌難以掌握,因此錯誤更容易發生。對於分散式系統的運維而言,這幾乎是個惡夢般的挑戰。隨著時間的推移,我們會逐漸認識到以下幾點:

  1. 故障發生並不可怕,恢復時間過長才是真正的問題。在分散式系統中,故障幾乎是不可避免的,但如果恢復時間過長,就會對業務產生嚴重影響。
  2. 故障發生並不可怕,影響範圍過大才令人擔憂。在設計分散式系統時,我們需要盡量控制故障的影響範圍,避免單點故障所帶來的連鎖反應。

維運團隊在分散式系統中面臨巨大的壓力,幾乎時刻都在處理各種大小不一的故障。許多大公司試圖透過增加大量的監控指標來應對這些問題,有時甚至設置了幾萬個監控點。但這種做法其實是「蠻力」策略。一方面,過多的資訊會導致資訊過載,反而難以取得有價值的洞察;另一方面,服務等級協定(SLA)要求我們定義關鍵指標(Key Metrics),也就是最為重要的效能和狀態指標。然而,許多公司在實際操作中忽略了這一點。

這種做法反映了維運思維上的惰性,因為它只專注於「救火階段」而不是「防火階段」。正所謂“防火勝於救火”,我們需要在設計和運維系統時預先考慮故障的發生,即所謂的“設計即為容錯”(Design for Failure)。在系統設計中,就應考慮如何減輕故障的影響。如果無法完全避免故障,也應透過自動化手段盡快恢復故障,將影響控制在最小範圍內。

隨著系統中機器和服務數量的增加,我們會發現人類的限制逐漸成為管理的瓶頸。人類無法對複雜系統進行面面俱到的管理,而這時,自動化手段就顯得格外重要。 “人管理代碼,代碼管理機器,人不直接管理機器”,這一理念意味著我們應通過自動化和代碼治理來管理分佈式系統的複雜性,將人的作用集中在管理代碼和策略上,而將具體的系統操作交由自動化系統執行。這種方式不僅能夠提升系統的穩定性和可控性,還能有效減輕維運團隊的負擔。

問題四:多層架構的維運複雜度更大

通常情況下我們可以將系統分為四個層次:基礎層、平台層、應用層和存取層。

  1. 基礎層包括機器、網路和儲存設備等基礎設施。
  2. 平台層指的是中間件層,包含Tomcat、MySQL、Redis、Kafka 等軟體。
  3. 應用層包含各種業務軟體和功能服務。
  4. 接入層負責用戶請求的接入,如網關、負載平衡、CDN 和DNS 等。

對於這四個層次,我們需要明確一點:任一層出現問題,都會影響整個系統的運作。如果沒有一個統一的視圖和管理機制,各層的維運就會被割裂,導致更大的複雜性。

許多公司在團隊分工上是依照技能來劃分的,例如產品開發、中介軟體開發、業務維運、系統維運等子團隊。這種分工方式會導致每個團隊只專注於自己負責的部分,造成整個系統缺乏協同,資訊不良。當某個環節出現問題時,整個系統就像「骨牌」一樣,一個故障會引發連鎖反應,影響範圍不斷擴大。

由於缺乏統一的運作視圖,團隊無法清楚地了解一個服務呼叫在每個層次和資源中是如何流轉的。因此,當故障時,定位問題和溝通協調會消耗大量時間和精力。先前我在某雲端平台工作時曾遇到類似的情況:從存取層到負載平衡,再到服務層和作業系統層,每個環節的KeepAlive 參數設定不一致,導致軟體的實際運作行為與文檔描述不符。工程師在排查問題的過程中屢屢受挫,以為解決了一個問題,結果又出現了新的問題。經過多次重複排查和調試,最終才將所有KeepAlive 參數設定成一致,耗費了大量時間。

由此可見,分工本身不是問題,問題在於分工後的協作是否統一與規範。這一點尤其值得重視。在系統運作中,確保各層級之間的協調一致、標準化管理以及對系統的整體視圖掌握,是避免維運混亂、提高效率和系統穩定性的關鍵。