題記:這篇開始逐一深入介紹各個構件塊,從服務調用開始
原理
所謂服務調用,就是通過這個構件塊讓你方便的通過HTTP或者gRPC協議同步調用其他服務的方法,這些方法也是通過HTTP或者gRPC來暴露的。而方便的含義在於,你無需擔心如下問題:
- 如何發現其他服務,不用關心調用的鏈路以及負載均衡
- 調用過程中如何保證安全性
- 在遇到瞬態錯誤或中斷的時候如何處理重試
- 如何記錄調用鏈路的跟蹤信息
Dapr本身並沒有提供額外的API讓你去利用這些特性,上面所有的一切都通過Sidecar模式幫你橫切到Dapr邊車實例中自動處理。如下圖所示:
你的服務對其他服務發起的一切服務調用都要經過Dapr邊車實例,其他服務接收的一切服務調用同樣也要經過Dapr實例。分別執行如下步驟:
- 如果服務A要對服務B發起調用(不管HTTP還是gRPC),其實調用的目標是服務A的Dapr邊車;
- Dapr會利用Dapr的命名解析組件(后續文章會介紹)來找到服務B的的Dapr邊車位置;
- 然后服務A的Dapr邊車就把調用請求轉發給服務B的邊車了;
- 由於服務B的邊車和服務B是配對的,知道服務B的調用信息(比如端口),所有請求被再次轉發給服務B,服務B完成服務調用的業務處理;
- 服務B處理完,把服務調用響應結果返回給服務B的Dapr邊車;
- 服務B的Dapr邊車返回響應結果給服務A的Dapr邊車;
- 服務A的Dapr邊車最后把響應結果返回給服務A本身。
能力
從上面的原理可以看出,通過成對的Dapr邊車,來作為服務之間調用的中介,就可以簡化服務和Dapr邊車之間的調用方式,就可以強化邊車之間的調用方式。
這什么意思呢?就是Dapr把服務調用之間的一些共性且復雜的問題幫你解決掉(兩個邊車之間的調用),你只用采用最基本的HTTP和gRPC功能來暴露你的服務或者調用你的服務(服務與邊車之間的調用)。由此,你可以獲得Dapr給你提供的如下能力:
- 尋址和負載均衡:Dapr自動幫你找到要調用的目標服務,並自動對目標服務的多個實例進行負載均衡。
- 命名空間范圍限定:可以把服務放到特定的命名空間內,從而方便隔離各類服務。這個能力最常見的用途就是用命名空間來限定運行環境(開發、測試、生產等)。不過這個能力和托管環境有關,目前只有Kubernetes支持。
- 重試:在分布式環境中,遠程服務出現瞬態故障是很常見的(可能由網絡、負載、安全等因素造成),所以在微服務架構中針對同步服務調用必須實現重試機制。傳統的方式下,(就算有重試框架的幫助下)需要在業務邏輯代碼中編寫很多冗長的重試代碼。通過Dapr邊車內置的重試機制極度簡化了這個問題。目前Dapr的會間隔1秒最多重試3次。
- 安全通信:分布式環境,通信的安全性也是一個需要重點關注的領域。Dapr提供了一個名為Sentry的基礎服務,讓邊車之間的通信基於mTLS來進行安全保證(mTLS的證書會自動更新)。
- 安全訪問:在安全通信的mTLS證書的基礎,可以通過配置信任域(TrustDomain)和應用標識(App Identity)來進行訪問控制。在這里暫時不對此話題展開。
- 可觀測性:默認情況下,Dapr會收集邊車之間服務調用的度量和跟蹤信息,從而幫助開發人員來洞察和診斷應用程序。也就是說,高大上的分布式跟蹤直接由Dapr提供內置支持了。
- 可替換的服務發現:原理里面提到Dapr之間的服務發現會依賴於一個稱之為命名解析組件的東西,實際上這個東西可以在不同的托管環境中進行替換。默認情況下,在Kubernetes里面,是使用DNS Service來作為命名解析組件的實現。
規范
由於服務調用這個構件塊並沒有為服務應用提供什么可直接訪問的能力,所以整個規范也相對簡單,僅僅規定了調用其他應用的URL模式,即通過如下地址來發送HTTP請求(或gRPC請求):
POST/GET/PUT/DELETE http://localhost:<daprPort>/v1.0/invoke/<appId>/method/<method-name>
上面的URL地址涉及到幾個約定好的參數:
- daprPort:這是Dapr邊車暴露的HTTP端口(默認50001)或者gRPC端口(默認3500);可以通過
dapr run
的--dapr-grpc-port
或--dapr-http-port
來設置;應用內可以通過DAPR_HTTP_PORT
或DAPR_GRPC_PORT
這兩個環境變量來獲得端口值。 - appId:這是目標應用的AppId,在命名空間(如果有)內唯一的標識;可以通過
dapr run
的--app-id
來設置。 - method-name:這是需要調用的目標應用的接口名稱,一般是根路徑(比如
/hello
)或者嵌套路徑(比如/api/weather
)也是支持的。
DOTNET SDK
作為DOTNET博主,我就僅介紹DOTNET SDK的情況。由於服務調用規范本身就簡單,所以SDK也相對簡單。對於被調用端,目前並沒有提供任何輔助的能力,你只需要使用適合的現成框架來暴露HTTP或者gRPC端點。
對於調用端,提供了一個客戶端類 DaprClient,有如下方法來幫助你發送服務調用的請求:
- InvokeMethodAsync
- InvokeMethodRawAsync
- InvokeMethodWithResponseAsync
對於DaprClient具體的用法可以參見這里的示例代碼:https://github.com/dapr/dotnet-sdk/blob/master/samples/Client/DaprClient/Program.cs#L217
internal static async Task InvokeDepositServiceOperationAsync(DaprClient client)
{
Console.WriteLine("Invoking deposit");
var data = new { id = "17", amount = (decimal)99 };
// Invokes a POST method named "depoit" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("invoking");
var a = await client.InvokeMethodAsync<object, Account>("routing", "deposit", data, HttpInvocationOptions.UsingPost());
Console.WriteLine("Returned: id:{0} | Balance:{1}", a.Id, a.Balance);
Console.WriteLine("Completed");
}
用法與例子
要了解服務調用構件塊具體如何使用的,照着官方文檔做就是了。
對於不想看英文文檔的同學,可以關注我們Dapr中文社區的翻譯過程(也歡迎加入):https://github.com/dapr-cn/docs
另外我在單獨寫一個Dapr的Quickstarts(正在逐步完善中),大家可以參考:https://github.com/heavenwing/dapr-dotnet-quickstarts/tree/main/ServiceInvocation
彩蛋——如何暴露gRPC端點
最后,官方文檔里面其實沒有把如何暴露gRPC端點這個話題講清楚,不過在SDK中其實已經把dapr的Protobuf 封裝好了(其實是自動生成好了),你引用了SDK中的Dapr.Client包就可以直接使用。我之前根據dapr的protobuf協議實現了一個例子,其實就是實現 AppCallback.AppCallbackBase
的 Task<InvokeResponse> OnInvoke(InvokeRequest request, ServerCallContext context)
方法,並通過ASP.NET Core來托管。 代碼已經合並到SDK中的samples部分,見:https://github.com/dapr/dotnet-sdk/blob/master/samples/AspNetCore/GrpcServiceSample/Readme.md。調用代碼見:https://github.com/dapr/dotnet-sdk/blob/master/samples/Client/DaprClient/Program.cs#L298
后來我覺得這個示例更加類似quickstarts,而不是SDK的示例,后面我會把這個示例添加到我的quickstarts中,並在SDK中去實現一個真正進行gRPC端點暴露開發的輔助能力,敬請期待。