微服務與分佈式系統設計看這篇就夠了!

後臺分佈式架構形形色色,特別是微服務和雲原生的興起,誕生了一批批經典的分佈式架構,然而在公司內部,或者其他大型互聯網企業,都是拋出自己的架構,從接入層,邏輯層,數據層都各有特點,但這些系統設計中到底是出於何種考量,有沒有一些參考的脈絡呢,本文將從雲原生和微服務,有狀態服務,無狀態服務以及分佈式系統等維度探討這些脈絡。

01 分佈式系統概論

下面這個定義來自於經典的《Designing Data-Intensive Application》:

一個涉及通過網絡進行通信的多臺機器的系統被稱爲分佈式系統。需要分佈式系統,一般是希望能從分佈式系統達到以下的好處:

分佈式系統雖然能帶來這些好處,但是分佈式系統也存在很多挑戰。通過網絡傳輸的每個請求和 API 調用都需要處理可能發生的故障:網絡可能中斷,服務可能過載或崩潰,請求超時。

但 DDIA 定義主要是基於有數據有狀態來討論分佈式,但是從現實的實踐中,分佈式系統存在有狀態和無狀態兩種:

n59rNO

爲什麼我們要重點說有狀態的分佈式架構?

目前大部分業務應用場景(特別是 to c 業務)需要存儲用戶的數據以及業務數據,本質還是數據密集型系統,也就是有狀態的服務,那麼如何設計好一個有狀態的分佈式架構,是大部分業務都會面臨的共同問題,也是本文的重點。

這裏給出設計有狀態分佈式架構,需要的一些考慮:

02 實現分佈式系統的模型

下面的圖都涉及到兩個概念,這兩個概念很多業務分層的概念,本文用這個分層來作爲例子討論:

AO: 封裝了應用程序的業務邏輯和處理流程。AO 主要負責處理用戶請求,調用相關的原子服務來完成特定的任務。它通常位於微服務的頂層,與其他對象進行交互,協調不同的功能模塊‌。

BO: 微服務中相關的原子服務,負責業務原子化的服務,通過被各種 AO 服務調用。功能可以是某個特定的業務原子功能,也可以是和數據打交道的原子服務。

實現有狀態的分佈式系統,通常有以下三種:

2.1 單體應用

單體架構是一種傳統的架構方式,應用程序作爲一個整體進行開發、測試和部署。

優點

面臨的問題

2.2 SOA 架構

SOA 架構更多是關注於改變 IT 服務在企業範圍內的工作方式,SOA(面向服務的架構)定義了一種可通過服務接口複用軟件組件並實現其互操作的方法。簡單舉個例子,使用 SOAP(簡單對象訪問協議)/HTTP 或 Restful HTTP (JSON/HTTP) 等標準網絡協議來公開服務算是 SOA 的一種。

優點

面臨的問題

2.3 微服務

微服務架構是一種雲原生架構常用的實現方式,在這種方法中,單個應用程序由很多鬆散耦合並能夠獨立部署的更小組件或服務組成。一般認爲是 SOA 架構的一種變體,一般也會使用 SOA 的原則來設計接口,但微服務更強調基於雲原生,獨立部署,和 devops,持續交付是一脈相承的。

優點

除了類似於 SOA 的優點,還有以下優點

對比 SOA,主要是在部署和運維難度上提升了,但是又引入了以下一些需要解決的問題:

  1. 必須有接入層:如上圖,微服務化後,必然存在用戶需要直接鏈接後端服務,那麼這個時候就需要網關來解耦這塊,也就是上面接入層討論的好處。

  2. 服務容錯:多個微服務部署在雲上,不同母機,會帶來通訊的複雜性,網絡問題會成爲常態,那麼如何容災,容錯,降級,也是需要考慮的。

  3. 服務發現:當服務 A 發佈或者擴縮容時,依賴服務 A 的服務 X 如何在保持運行的前提下自動感知到服務 A 的變化。這裏需要引入第三方服務註冊中心來實現服務的可發現性。比如北極星,stark,以及如何和容器,雲原生結合。

  4. 服務部署:服務變成微服務之後,部署是分散,部署是獨立的,就需要有一個可靠快速的部署,擴縮容方案,也包括 ci/cd,全鏈路、實時和多維度的可觀測性等,如 tke,智妍等,k8s 就是解決這種問題的。

  5. 數據存儲隔離:數據存儲隔離 (Data Storage Segregation, DSS) 原則,即數據是微服務的私有資產,必須通過當前微服務提供的 API 來訪問數據,避免數據層產生耦合。對於有狀態的微服務而言,通常使用計算與存儲分類的方式,將數據下層到分佈式存儲方案中,從而一定程度上實現服務無狀態化。

  6. 服務間調用:服務 A 採用什麼方式纔可以調用服務 X,由於服務自治的約束 ,服務之間的調用需要採用開發語言無關的遠程調用協議。現在業界大部分的微服務架構通常採用基於 IDL (Interactive Data Language, 交互式數據語言)的二進制協議進行交互,如 pb。

通過上面討論,目前微服務來實現分佈式系統是比較符合雲原生的趨勢,所以下面的討論主要是基於微服務化的設計。

下面針對微服務設計需要解決的問題,一起探討下上面微服務需要解決的 1-5 個方面在系統該如何實現(第 6 點方案比較明確,這裏不展開講述):

03 接入層解決了什麼問題?

從上面單體應用架構,微服務架構,可以看出明顯的差別,就是單體應用架構每個服務都可以提供完整的服務,那麼用戶和後臺鏈接數可以通過服務的無限擴容得到滿足,但是微服務不行,微服務的每個對外的服務接口都得和用戶建立鏈接,就會存在兩個最主要的問題:

  1. 鏈接爆炸,有多少對外的微服務就多幾倍的鏈接

  2. 服務和用戶端耦合嚴重,用戶端得知道哪個請求發往哪個服務,缺乏統一路由。

那麼這個時候引入一箇中間層隔離用戶和後端服務是非常必要,這個中間層負責對接用戶的鏈接,然後把請求往業務服務上發。同時考慮服務的用戶有地域性,可以把中間層分成兩個部分:

  1. 區域化網絡接入層:負責區域化網絡接入,跟用戶地域就近。

  2. 業務網關:負責服務透明代理,命令字的轉發等。

這時微服務架構就如下:

3.1 區域化網絡接入層

這一層需要解耦的重點是把用戶的實際地域 和 業務的邏輯 RS 對應起來,同時避免每個業務都去建設類似的架構,同時解決用戶體驗,讓用戶儘量訪問離用戶最近的業務機器,減少耗時。

所以用戶到區域化網絡層應該是地域就近,區域化網絡層到業務網關也是就近,深圳對應深圳,上海對應上海,並且考慮機房間就近。

3.2 業務網關

從上面分析可以看到,業務網關的作用最主要是透明服務代理(命令字轉發),但業務網關還能提供如下作用:

業務網關的主要構成有:具有代理用戶的接入服務 + 具有路由的轉發服務。

路由實現又包含三個部分,路由存儲、路由計算、路由容災。

04 微服務的容錯

4.1 條帶化

爲啥說微服務的服務容錯能力,要到這個概念?

微服務在部署上,特別是在雲上,一種服務進行多份同構部署,以提供容災的可能性,如果多種微服務的部署機房是無序的,那麼在故障出現時,就很容易出現服務的調用鏈路,部分正常,部分故障,就算故障服務被剔除系統外,鏈路也是來回穿梭在多個機房,耗時和故障影響都不可控。(考量一些服務半死不活,也會形成二次打擊)。

所以一個比較簡潔的方案是一個完整的服務集羣部署在同一個物理單元,在物理單元的粒度上進行服務的容錯,同時在同一個物理單元內,微服務內的時耗也是最低的。

把一個完整服務集羣部署在同一個物理單元(或者多個物理單元構成的邏輯單元),並且隔離流量的做法叫做條帶化,其中每一個條帶化的單元也叫做 set,也有人稱之爲單元化和邏輯單元,下文主要以條帶化和 set 來表述。

使用條帶化的好處:

那麼是不是自上而下都是條帶化(包括邏輯層,數據層),就可以保證業務整體都有容災能力呢?我們來對比下。

自上而下全部條帶化

這個方案是把業務關心的內容都包括在條帶化容災裏面,但從上面可以直接得看出因爲用戶存儲數據不一定是在就近接入的業務網關的那個 set,所以業務網關實際也是有可能迴路由到其他服務,這裏其實就破壞了條帶化。這種條帶化沒有達到流量隔離的目的。比如某個 set 的 ao 耗時變慢了,所有 set 的業務網關會收到影響,這個 set 所在機器也有可能受影響,進而導致這個 set 的 ao 和 bo 也受影響。故障會擴散。

除此之外,這種方案還有以下缺點:

接入層以下條帶化

這個方案是把邏輯層和數據層放在 set 裏面。

這種方案的缺點:

僅邏輯層條帶化

這種方案,便是邏輯層 看作是無狀態的微服務集羣,把有狀態的界限盡量限定在數據層,避免耦合邏輯層比較多的無狀態服務。在設計微服務時,應當遵守無狀態服務設計原則,與底層基礎設施解耦,從而實現在不同容器之間自由調度。對於有狀態的微服務而言,通常使用計算與存儲分類的方式,將數據下層到分佈式存儲方案中,從而一定程度上實現服務無狀態化。

這樣做的可以解決上面兩種方案 數據層耦合限制 ao 和 bo 擴容 的缺點,但是爲了保證儘早跨城,儘量減少跨城,業務網關路由可以和數據庫 proxy 的路由具有一定 聯動關係。儘可能在業務網關路由時通過用戶地域路由(在用戶地域存儲數據也是最優方案)的方式減少跨城。

具體的聯動方式說明:

4.1.1 條帶化粒度選型

既然微服務的容災單元是 set,解決的主要是物理級別的故障,那麼 set 條帶化粒度的選型有哪些考量,下面給個表格參考:

UwJdxP

05 服務發現

微業務服務的互相調用,主要是業務網關發現 set,set 內微服務的互相發現,這兩個都可以通過服務發現來做。

爲了保證條帶化(物理隔離,流量隔離),以及 set 內的完整性,服務發現時,應該儘量在條帶化粒度內服務可以互相發現,條帶化粒度外服務不能互相發現(容災降級應該由 set 間的容災策略決定),我們選擇的服務發現組件一定要支持的一個能力。

在雲原生的場景,一般存在兩種服務發現實現方案,集中式服務發現,以及服務網格。

集中式服務發現與服務網格的主要區別在於它們解決的問題範圍和實現方式。‌

集中式服務發現是一種服務註冊和發現機制,通過一箇中心化的服務註冊表來管理所有服務的 IP 地址和相關信息。每當服務上線或下線時,它會向註冊表註冊或註銷自己,其他服務在需要時查詢註冊表以獲取其他服務的地址。這種方式簡化了服務之間的通信,但需要維護一箇中心化的服務註冊表,可能成爲單點故障,並且需要處理網絡延遲和單點故障問題‌。

服務網格則是一種專用的基礎設施層,用於管理分佈式應用程序中各個微服務之間的通信。它通過部署在應用服務旁邊的代理(稱爲 sidecar)來處理服務之間的通信,提供服務發現、負載均衡、流量路由、身份驗證和可觀測性等功能。服務網格將網絡通信的複雜性抽象化,使開發人員能夠專注於應用程序邏輯,而不必處理底層的網絡代碼。它通過分佈式的方式提供服務,避免了單點故障,並且提供了更高的靈活性和可擴展性‌。

總結來說,集中式服務發現通過中心化的方式簡化服務之間的通信,但可能面臨單點故障和網絡延遲的問題;而服務網格通過分佈式代理網絡提供更高級的通信管理功能,具有更高的靈活性和可擴展性。

雖然有可能因爲集中式服務發現故障不可用,但可以通過本地 SDK 接入提供本地緩存和異步更新的功能,也能一定程度上緩解單點依賴。相對來說如果服務網格的控制面故障了,其實也在短時間內沒辦法更新容器的網絡策略。

06 擴容

如上所說微服務的部署是一個難題,微服務化後,服務數量都會比原來單服務數量多了十幾倍,那麼自動化部署,自動化擴容就成爲微服務化一個必要項。所幸的是伴隨着雲原生的發展,容器編排系統也在飛速發展,其中 kubernetes 是代表性的容器化部署編排系統。

那麼在支持條帶化上,kubernetes 應該需要支持 **set 內機器橫向擴容,**set 間也能橫向擴容。

要達到這個目的,一般需要幾方面的支持:

07 數據存儲

微服務還有一個需要解決的問題,就是數據服務隔離的問題,數據存儲是系統有狀態的來源,可以分爲以下兩種情況討論:

  1. 如果是數據存儲是全局類的(寫點只有一個),那麼對於寫,上層任何 set 都得接入,必然存在跨城,容災時只能依賴數據存儲自己的主從切換。

  2. 如果數據存儲是分片的,一般都希望上層 set 和底層寫點儘量靠近,上文也提供,如果把數據分片和 set 綁定,是能最大程度靠近,但缺點是數據分片本身會限制 set 的擴容,也需要上層需要知道底層數據分片情況。

考慮數據分片的因素主要是包括以下考慮之一:

  1. 不能允許跨城的寫耗時。

  2. 單機存儲或者寫 io 不能滿足要求。

  3. 希望通過數據隔離,減少故障容災的影響,滿足合規要求等。

這裏提供一種指導:

  1. 數據分片和 set 不綁定,通過一層數據 proxy 解耦和 set 的關係。

  2. 數據存儲採用分佈式存儲系統,邏輯服務訪問數據層通過就近訪問(一般數據 proxy 這一層,分佈式存儲系統都會提供)。

  3. 如果有嚴格地域就近要求,底層存儲系統可以聯動接入的路由來做盡可能的就近。把數據分片和部署關係通過路由數據來解耦,而非分散在邏輯服務中,讓邏輯服務知道底層存儲部署。

08 總結

經過上面討論,一種有狀態的分佈式系統設計的整體架構圖可以如下:

8.1 各層的容災分析

以下都是基於機器足夠的情況,性能機器預留就按照每個業務進行評估。

區域網絡化接入層

  1. 單機故障:直接剔除出 CDN。

  2. 機房故障:直接把機房的機器都剔除出 CDN。

  3. 城市故障:把 CDN 暫時指向另一個城市

業務網關

  1. 網關單機故障:區域網絡接入層剔除這些機器,可以通過監控錯誤自動剔除

  2. 網關機房故障:區域網絡接入層剔除故障機房,可以通過監控錯誤自動剔除

  3. 網關城市故障:區域網絡接入層把流量導到正常的城市,剔除掉故障城市的流量,這種情況下故障期間,會存在二分之一流量增加一次跨城耗時,理論上也可以實現自動剔除,但這個操作比較重,也需要進行慎重評估,建議是手動切。

邏輯層

數據層的容災切換,一方面可以依賴邏輯層訪問 db 故障後,返回給業務網關的錯誤碼來實現從邏輯層開始切換,一方面可以依賴分佈式存儲系統自己的切換能力。

降級策略

業務網關也有一些其他應對故障的降級方案,如果在整體系統故障的情況下,可以這麼處理降級:

原創作者|黃楠駒

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/wg_EkeogSkjGaChvsLsaVw