Dapr 實際上是把分散式系統 與微服務架構實踐的挑戰以及k8s 這三個主題的全方位的設計組合,特別是Kubernetes設計模式 一書作者Bilgin Ibryam 提出的Multi-Runtime Microservices Architecture,中譯參見敖小劍的部落格: [譯] 多執行時微服務架構。
分散式系統 和微服務架構實踐的核心問題就是要解決系統複雜性這個難題,降低複雜性的通常做法就是分而治之,Dapr的最核心的設計就是Sidecar Pattern + Building Block,如下圖:
圖片來源:https://docs.microsoft.com/zh-cn/dotnet/architecture/dapr-for-net-developers/dapr-at-20000-feet
Sidecar Pattern
: 通過職責分離與容器的隔離特性,降低應用程式的複雜度。Building Block
: 類似於樂高搭積木方法,通過Dapr 提供的核心元件(Component),分離與抽象化系統架構。
Dapr 設計上幾乎和Bilgin Ibryam 提出的Multi-Runtime Microservices Architecture 不謀而合,它有幾個核心的設計點:
- Sidecar
- Building Block & Component
- Service Invocation
- Middleware
- State
基於上面的這些核心設計,Dapr 有了多執行時微服務架構 的特性,以此延伸出底下的重要功能,或者說設計模式:
- Security
- Observability: tracing, metrics, logs and health
- Pub / Sub / Batch Process
- Actors
- Secret Management
- Config Management 正在開發中……
Sidecar
Sidecar是非常重要的雲端計算設計模式,下面這張圖是 Sidecar 與Microservice 之間搭配後形成多個服務的關係圖,這樣的結構形成了服務網格的概念, Dapr 通過配置的方式,動態生成Sidecar ,隨後伴隨著App,一組Dapr Sidecar + App 的組合稱為Dapr App
Dapr App 在K8s 裡面的形態就是 Pod = (App_Container + Sidecar_Container)
同樣的概念,如果Dapr App跑在k8s外面,也就是自承載模式。 在自承載模式下,微服務和 Dapr sidecar 在沒有容器業務流程協調程式(如 Kubernetes)的單獨本地程序中執行。
每個Dapr App 都通過Sidecar 溝通,在通訊之前,Dapr App 要知道的是對方在哪?所以服務發現和服務呼叫是Dapr App 要解決的第一個問題,知道彼此在哪了,然後就是通訊模式,Dapr 支援HTTP / gRPC 兩種通訊模式。 Dapr App 之間的預設的通訊模式使用gRPC,也就是如果使用HTTP呼叫Dapr API,內部服務之間的通訊也會轉成gRPC。
gRPC 是一種新式的高效能框架,它通過 RPC(遠端過程呼叫) 改進。 gRPC 使用 HTTP/2 作為傳輸協議,該協議通過 HTTP RESTFul 服務提供顯著的效能增強,包括:
- 對通過同一連線傳送多個並行請求的多路複用支援 - HTTP 1.1 將處理限制為一次處理一個請求/響應訊息。
- 雙向全雙工通訊,用於同時傳送客戶端請求和伺服器響應。
- 內建流式處理,支援對大型資料集進行非同步流式處理的請求和響應。
若要了解有關詳細資訊,請檢視適用於 Azure 電子書的.NET Cloud-Native中的 gRPC概述。
Dapr Sidecar 有了服務呼叫、服務發現和通訊模式之後,定義出來了一個Building Block (構建塊)的概念,使用宣告的方式,定義多個元件Component 擴充套件Sidecar的能力,這些能力正是分散式系統需要面對的問題。
構建塊 和 元件
構建基塊封裝分散式基礎結構功能。 可以通過 HTTP 或 gRPC API 訪問該功能,目前版本有如下構建塊。
Buiding Block 是每個 Dapr Sidecar 可以擴充套件的概念,每個 Block 由多個 Components 組成,開發者可以自行設計、擴充套件 Component,然後貢獻給社群,這裡集中了社群貢獻的元件 https://github.com/dapr/components-contrib。 我們來看一下微軟的.NET團隊基於Dapr 設計的eshopondapr,圖中每個Dapr標示都是一個Component ,一共標記了六種:
基於這樣的設計,Dapr 把最核心的Component 提供了基於分散式系統的 最佳實踐 (Best Practice)和 設計模式
(Design Patterns)
- Input/Output Bindings:
- Pub / Sub:
- 全部列表:Supported pub/sub brokers Middleware: Dapr 的一種特殊 Components,後面介紹。
- Service discovery name resolution: Dapr 的特殊 Components,後面介紹。
- State Stores
- Secret Stores
這些核心的設計可以通過程式碼倉庫瞭解:
倉庫 https://github.com/dapr/components-contrib 是Dapr 官方開放的Component ,開發者可以通過 PR 提交來把 擴充套件的Component 貢獻給社群,目前已經有70 多個Components 可以使用,使用的時候要注意版本的階段性是在Alpha / Beta / GA,一定要做好風險評估。
服務呼叫和服務發現
這就是我們在微服務裡面常說的服務治理,Dapr 作為一個分散式系統,多個Dapr app怎麼知道彼此的存在,通過什麼方式進行溝通,這就是Dapr的服務治理要解決的問題,Dapr的服務發現機制,按照架構的不同方式(k8s還是自託管)有不同的實現,官方文件(https://docs.dapr.io/zh-hans/developing-applications/building-blocks/service-invocation/service-invocation-overview/)裡的這張圖介紹了呼叫邏輯
服務 A 對服務 B 發起HTTP/gRPC的呼叫。
Dapr 使用 name resolution component 發現 Service B’s 位置 取決於執行的環境 hosting platform.
Dapr 將訊息轉發至服務 B的 Dapr 邊車
注: Dapr 邊車之間的所有呼叫考慮到效能都優先使用 gRPC。 僅服務與 Dapr 邊車之間的呼叫可以是 HTTP 或 gRPC
服務 B的 Dapr 邊車將請求轉發至服務 B 上的特定端點 (或方法) 。 服務 B 隨後執行其業務邏輯程式碼。
服務 B 傳送響應給服務 A。 響應將轉至服務 B 的邊車。
Dapr 將訊息轉發至服務 A 的 Dapr 邊車。
服務 A 接收響應。
這裡面有很多核心的概念:
- Dapr 命名有Namespace 概念,基本格式為FQDN
- Dapr 的Service Invocation 支援 gRPC / HTTP 兩種方式,預設的 Service to Service 使用gRPC 通訊。
- Service to Service 使用mTLS 做傳輸層加密
- 支援 Service 的ACL,可以個別控制每個API 與Method 的操作
- 支援 Retry 機制
- 可以使用其他service discovery 實現
- RR load balancing with mDNS
- 支援tracing 和 metric
Middleware
和ASP.NET Core 支援通過 middleware 處理 HTTP request / response 完成一些 Cross-Cutting (AoP) 的功能,Dapr 也支援 Middleware 的概念,如下圖:
Dapr 允許通過連結一系列中介軟體元件來定義自定義處理管道。 請求在路由到使用者程式碼之前經過所有已定義的中介軟體元件,然後在返回到客戶機之前,按相反順序經過已定義的中介軟體,如下圖中所示。
Actors
Actor 模型 起源於Carl Hewitt 在 1973 年提出的作為併發計算的概念模型,這種形式的計算會同時執行多個計算。 當時並沒有高度並行的計算機,但多核 Cpu 和分散式系統的最新進步使得Actor 模型 變得流行。在Actor 模型中,Actor 是一個計算和狀態獨立的單元。 Actors 完全彼此隔離,它們永遠不會共享記憶體。 Actors 使用訊息相互通訊。 當一個Actor 收到訊息時,它可以更改其內部狀態,並將訊息傳送到其他 (可能是新的) Actors。
Actor模型使得編寫併發系統變得更簡單的,它提供了基於 turn-based 的 (或單執行緒) 訪問模型。 多個Actors可以同時執行,但每個Actor 一次只處理一個接收的訊息。 這意味著,在任何時候,都可以確保在Actors 中最多有一個執行緒處於活動狀態。 這使得編寫正確的併發系統和並行系統變得更加容易。
Dapr 的實現基於 專案 "Orleans" 中引入的虛擬Actor模式。 對於虛擬Actor模式,不需要顯式的建立Actor。 第一次將訊息傳送到Actor時,Actor將被隱式啟用並放置在群集中的節點上。 當不執行操作時,Actor 會以靜默方式從記憶體中解除安裝。 如果某個節點出現故障,Dapr 會自動將啟用的Actor 移到正常的節點。 除了在Actor之間傳送訊息以外,Dapr Actor模型還支援使用計時器和提醒排程將來的工作。
雖然Actor模型 提供了很大的優勢,但必須仔細考慮Actor的設計。 例如,如果多個客戶端呼叫相同的Actor,則會導致效能不佳,因為Actor 操作會按順序執行。 下面的檢查清單是是否適用於 Dapr Actor的一些標準:
- 問題空間涉及併發性。 如果沒有Actor,則需要在程式碼中引入顯式鎖定機制。
- 可以將問題空間分割槽為小、獨立和隔離的狀態和邏輯單元。
- 不需要低延遲的讀取Actor 狀態。 因為Actor 操作是按順序執行,不能保證低延遲讀取。
- 不需要在一組Actor 之間查詢狀態。 跨Actor 的查詢效率低下,因為每個Actor 的狀態都需要單獨讀取,並且可能會導致不可預測的延遲。
滿足這些條件的一種設計模式非常好,就是 基於業務流程的 saga 或 流程管理器 設計模式。 Saga 管理必須執行的一系列步驟才能達到某些結果。 Saga (或程序管理器) 維護序列的當前狀態,並觸發下一步。 如果一個步驟失敗,saga 可以執行補償操作。 利用Actor,可以輕鬆處理 saga 中的併發,並跟蹤當前狀態。 EShopOnDapr 參考應用程式使用 saga 模式和 Dapr Actor來實現排序過程。
為了提供可伸縮性和可靠性,將在Actor服務的所有例項中對actor進行分割槽。 Dapr placement 服務負責跟蹤分割槽資訊。 啟動Actor 服務的新例項時,Sidecar 會將支援的Actor 型別註冊到placement 服務。 placement 服務計算給定Actor 型別的更新分割槽資訊,並將其廣播給所有例項。
總結
分散式架構的門檻比較高,需要考慮的問題很多,通常我們都需要考慮如下問題。
服務治理
:包含Service Invocation、Service Trusted and Authorization (服務的信任、認證與授權)、通訊模式(HTTP / gRPC)、通訊機制(Push / Pull)、狀態管理(State Machine)運維
:高可用、擴充套件機制、Log 處理、分散式追蹤、Metric安全性
:Data Encryption、Secret Management、KMS、Auth 整合效能和可靠性
:Rate Limit、降級、熔斷…可擴充套件的架構
如何提升開發團隊的效能
上述的這些東西,通常是一個有經驗的、資深的軟體工程師,如何在資源有限的情況下,可以快速開發、容易測試,是很多技術人的痛點所在。
這些問題從個別來看,都有相當成熟的系統,如果個別看,有很多現成的實踐可以參考。但是對於存在了幾十年的祖傳程式碼的系統架構而言,如果要進行微服務改造,往往都要傷筋動骨,讓技術主管和架構師傷透腦筋,往往要面對新舊技術的整合,同時也要面對現實的團隊需求的互動和對於新技術的學習門檻。對於開發應用程式的開發人員來講,滿足業務需求的開發已經夠頭痛了,還要考慮這麼多系統架構層面的東西,這種事是無法靠熱情填補的。Dapr 將一些經過驗證的技術和最佳實踐帶到微服務開發中。它通過即插即用模型將90 年代的資料驅動的客戶端/伺服器應用程式的操作,應用於現代雲原生應用程式所需的最常見服務,讓我們集中於業務需求的開發,而不需要考慮系統架構層面的東西 。
Dapr 正式釋出已經過去了半年時間了,現在最新版本是1.3.0. 下圖是技術採用生命週期,在早期採用者和早期大眾的中間,有一個死亡之井,無法越過死亡之井,Dapr已經跨過了死亡之井,你可以採用Dapr了。