手把手教你學Dapr – 4. 服務調用

介紹

通過使用服務調用,您的應用程式可以使用標準的gRPC或HTTP協議與其他應用程式可靠、安全地通訊。

為什麼不直接用HttpClientFactory呢

先問幾個問題:

  • 如何發現和調用不同服務的方法
  • 如何安全地調用其他服務,並對方法應用訪問控制
  • 如何處理重試和瞬態錯誤
  • 如何使用分散式跟蹤指標來查看調用圖來診斷生產中的問題

此時你會發現這些事情HttpClientFactory沒有幫你完成,而在微服務中這些又是必不可少的能力,接下來看看服務調用都做了什麼

服務調用如何工作的

先看一下兩個服務之間的調用順序

service-invocation-overview.png

  1. 服務A 向服務B發起一個HTTP/gRPC的調用。調用轉到了本地的Dapr sidecar

  2. Dapr使用名稱解析組件發現服務B的位置

  3. Dapr 將消息轉發至服務 B的 Dapr sidecar

    : Dapr sidecar之間的所有調用都通過gRPC來提高性能。 僅服務與 Dapr sidecar之間的調用可以是 HTTP或gRPC

  4. 服務B 的 Dapr sidecar將請求轉發至服務B 上的特定端點 (或方法) 。 服務B 隨後運行其業務邏輯程式碼

  5. 服務B 發送響應給服務A。 響應將轉至服務B 的Dapr sidecar

  6. Dapr 轉發響應至服務A 的 Dapr sidecar

  7. 服務 A 接收響應

命名空間作用域

默認情況下,調用同一個命名空間的其他服務可以直接使用AppID(假設是:nodeapp)

localhost:3500/v1.0/invoke/nodeapp/method/neworder

服務調用也支援跨命名空間調用,在所有受支援的宿主平台上,Dapr AppID遵循FQDN格式,其中包括目標命名空間。

FQDN:(Fully Qualified Domain Name)全限定域名:同時帶有主機名和域名的名稱。(通過符號「.」)

例如:主機名是bigserver,域名是mycompany.com,那麼FQDN就是bigserver.mycompany.com

註:FQDN是通過符號.來拼接域名的,這也就解釋了AppID為什麼不能用符號.,這裡不記住的話,應該會有不少小夥伴會踩坑

​ 比如.net開發者習慣用 A.B.C 來命名項目,但AppID需要把.換成-且所有單詞最好也變成小寫 (a-b-c),建議把它變成約定遵守

比如調用命名空間:production,AppID:nodeapp

localhost:3500/v1.0/invoke/nodeapp.production/method/neworder

這在K8s集群中的跨名稱空間調用中特別有用

服務間安全性

通過託管平台上的相互(mTLS)身份驗證,包括通過Dapr Sentry服務的自動證書轉移,可以確保Dapr應用程式之間的所有調用的安全。 下圖顯示了自託管應用程式的情況。

訪問控制

應用程式可以控制哪些其他應用程式可以調用它們,以及通過訪問策略授權它們做什麼。 這使您能夠限制具有個人資訊的敏感應用程式不被未經授權的應用程式訪問,並結合服務到服務的安全通訊,提供了軟多租戶部署。

具體的訪問控制後續章節會介紹

重試

在調用失敗和瞬態錯誤的情況下,服務調用執行自動重試,並在回退時間段內執行。

註:自動重試,默認是開啟的,可以關。但如果不關且業務又不支援冪等是很危險的。建議服務的介面要設計支援冪等,這在微服務里也是一個標配的選擇。

導致重試的錯誤有:

網路錯誤,包括端點不可用和拒絕連接。

由於在調用/被調用的Dapr sidecars上更新證書而導致的身份驗證錯誤。

每次呼叫重試的回退間隔為1秒,最多為3次。 通過gRPC與目標Sidecar連接的超時時間為5秒

可插拔的服務發現

Dapr可以在各種託管平台上運行。 為了啟用服務發現和服務調用,Dapr使用可插拔的名稱解析組件。 例如,K8s名稱解析組件使用K8s DNS服務來解析集群中運行的其他應用程式的位置。 自託管機器可以使用mDNS名稱解析組件。 Consul名稱解析組件可以在任何託管環境中使用,包括K8s或自託管環境

劃重點,自託管機器使用mDNS,在開發環境中後面文章會推薦VS上的無縫開發體驗,就是基於mDNS的

但它有點點小問題,我們已經解決了。你只需要像開發一個控制台程式一樣,基於Minimal API開心的F5就可以了

建議還沒有了解Minimal API的小夥伴可以研究起來了,真香

使用mDNS進行輪詢負載均衡

一圖勝千言,就使用mDNS輪著調用

service-invocation-mdns-round-robin.png

可觀測性的跟蹤和指標

默認情況下,將跟蹤應用程式之間的所有調用,並收集指標,以提供應用程式的洞察力和診斷,這在生產場景中尤其重要。 這為您提供了服務之間調用的調用圖和指標。

服務調用API和gRPC代理

pythonapp 通過Dapr sidecar調用nodeapp,通過服務調用的API及gRPC代理依然是上面見到的那個調用流程,做到了語言無關

service-invocation-overview-example.png

使用HTTP調用服務

創建Assignment.Server

創建ASP.NET Core空項目,並修改launchSettings.json,讓啟動HTTP的啟動埠變為5000

profiles.Assignment.Server.applicationUrl 的值改為 “//localhost:6000;//localhost:5000

修改Program.cs文件

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/", () => Console.WriteLine("Hello!"));

app.MapGet("/Hello1", () =>
{
    Console.WriteLine("Hello World1!");
    return $"\"Hello World1!\"";
});

app.MapPost("/Hello2", () => Console.WriteLine("Hello World2!"));

app.Map("/Hello3", () => Console.WriteLine("Hello World3!"));

app.Run();

此時一共有4個服務

  • / :Post方法,列印Hello!

  • /Hello1:Get方法,列印Hello World1!,返回Hello World1!

    註:返回的類型要是Json字元串,方便SDK反序列化

  • /Hello2:Post方法,列印Hello World2!

  • /Hello3:不帶後綴表示適配所有方法,列印Hello World3!

先使用Dapr CLI來驗證一下

運行Assignment.Server:在目錄dapr-study-room\Assignment04\Assignment.Server打開命令行工具,並執行下面命令

dapr run --app-id assignment-server --app-port 5000 dotnet watch

細心的小夥伴應該可以發現與上一篇的命令有一點點不同,dontet run變成了dotnet watch,這樣會開啟熱重載,方便調試

調用服務:再打開一個新的命令行工具,並執行下面命令

dapr invoke --app-id assignment-server --method /
dapr invoke --app-id assignment-server --method Hello1
dapr invoke --app-id assignment-server --method Hello2
dapr invoke --app-id assignment-server --method Hello3

可以發現4個命令都調用成功了,但是Assignment.Server輸出結果有點意外

== APP == Hello!
== APP == Hello World2!
== APP == Hello World3!

是的,沒有Hello World1!,那怎麼辦呢?我們把Hello1的命令改一下

dapr invoke --app-id assignment-server --method Hello1 --verb GET

invoke調用的輸出除了App invoked successfully以外還多了一行Hello World1!

與此同時Assignment.Server的輸出正確了

== APP == Hello World1!

除此之外invoke還有一些參數,比如--data,--data-file,喜歡研究Dapr CLI的小夥伴可以繼續嘗試。不過一般情況下用SDK就可以了

創建Assignment.Client

HTTP服務調用

創建控制台應用程式項目,使用NuGet包管理器添加Dapr.Client SDK,並修改Program.cs文件

using Dapr.Client;

var appId = "assignment-server";

var client = new DaprClientBuilder().Build();

await client.InvokeMethodAsync(appId, "/");

var resp = await client.InvokeMethodAsync<string>(HttpMethod.Get, appId, "Hello1");
Console.WriteLine($"Hello1 Response: {resp}");

await client.InvokeMethodAsync(appId, "Hello2");

await client.InvokeMethodAsync(appId, "Hello3");

看幾個細節

  • DaprClient是從DaprClinetBuilder Build出來的

    還有一種方式使用DaprClient,通過DI

    首先都是需要添加Dapr.AspNetCore NuGet包

    然後開始有分支了,如果是以前Web API的方式可以在Startup.cs文件ConfigureServices方法加入一行程式碼

    services.AddControllers().AddDapr();
    

    如果使用Minimal API默認是沒有Controllers的,那可以在var builder = WebApplication.CreateBuilder(args); 之後加入一行程式碼

    builder.Services.AddDaprClient();
    

    成功的注入進來了,在構造函數或者[FromServices]里愉快的玩耍吧

  • HttpMethod.Post 的我都沒有指定,默認就是Post

  • HttpMethod.Get的時候,返回值會自動用Json反序列化

    不喜歡Json?可以通過 DaprClient.CreateInvokeHttpClient 構造 HttpClient,聰明的你肯定知道後面怎麼辦了

註:

1. Minimal API雖香,但新,所以不是所有功能都支援,比如從參數中直接映射狀態管理,要等Minimal API支援Model Binder以後且SDK也同步支援了才可以
2. DaprClient是TCP的,也是執行緒安全的,可以大膽的復用,如果不用DI的話不需要頻繁構建DaprClient

驗證調用成功

使用命令行工具打開目錄dapr-study-room\Assignment04\Assignment.Client,然後執行命令

dotnet run

如果你不是用VS Code終端的PowerShell執行dapr run就可能遇到下面的錯誤

即便你沒有遇到也建議了解一下如何支援非默認埠

An exception occurred while invoking method: '/' on app-id: 'assignment-server'
 ---> System.Net.Http.HttpRequestException: 由於目標電腦積極拒絕,無法連接。 (127.0.0.1:3500)
 ---> System.Net.Sockets.SocketException (10061): 由於目標電腦積極拒絕,無法連接。

因為上面使用dapr run的時候沒有指定dapr http port,而默認client訪問的是3500埠

解決的辦法有兩種:

  1. 修改Assignment.Server啟動參數,增加--dapr-http-port 3500,這個方法治標不治本,因為將來我們可能啟動多個服務

    dapr run --app-id assignment-server --app-port 5000 --dapr-http-port 3500 dotnet watch
    
  2. 修改Assignment.Client的環境變數

    首先執行dapr list查看埠,以下面為例,HTTP PORT是51460

    APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID

    assignment-server 51460 51461 5000 dotnet watch 7s 2021-10-29 14:13.49 11676

    修改Assignment.Client的啟動參數,注意把51460換成你自己的埠,使用PowerShell執行下面命令

    $Env:DAPR_HTTP_PORT = 51460
    dotnet run
    

再執行一次dotnet run就可以看到正確的輸出結果了

Hello1 Response: Hello World1!

gRPC服務調用

篇幅太長了,舉一反三吧。就是調用InvokeMethodGrpcAsync,然後dapr-http-port換成dapr-grpc-port,DAPR_HTTP_PORT換成DAPR_GRPC_PORT

查看跟蹤

還記得dapr init的時候docker里有個zipkin吧,通過zipkin可以看一下調用跟蹤,通過瀏覽器打開下面地址

//localhost:9411/

此時頁面是空的

1.png

根據步驟操作一下就可以看到了

2.png

隨便點開一行數據尾部的SHOW,就可以看到調用詳情

3.png

本章源碼

Assignment04

//github.com/doddgu/dapr-study-room

我們正在行動,新的框架、新的生態

我們的目標是自由的易用的可塑性強的功能豐富的健壯的

所以我們借鑒Building blocks的設計理念,正在做一個新的框架MASA Framework,它有哪些特點呢?

  • 原生支援Dapr,且允許將Dapr替換成傳統通訊方式
  • 架構不限,單體應用、SOA、微服務都支援
  • 支援.Net原生框架,降低學習負擔,除特定領域必須引入的概念,堅持不造新輪子
  • 豐富的生態支援,除了框架以外還有組件庫、許可權中心、配置中心、故障排查中心、報警中心等一系列產品
  • 核心程式碼庫的單元測試覆蓋率90%+
  • 開源、免費、社區驅動
  • 還有什麼?我們在等你,一起來討論

經過幾個月的生產項目實踐,已完成POC,目前正在把之前的積累重構到新的開源項目中

目前源碼已開始同步到Github(文檔站點在規劃中,會慢慢完善起來):

MASA.BuildingBlocks

MASA.Contrib

MASA.Utils

MASA.EShop

BlazorComponent

MASA.Blazor

QQ群:7424099

微信群:加技術運營微信(MasaStackTechOps),備註來意,邀請進群

masa_stack_tech_ops.png