分散式應用框架 Dapr
微服務架構已成為構建雲原生應用程式的標準,微服務架構提供了令人信服的好處,包括可伸縮性,鬆散的服務耦合和獨立部署,但是這種方法的成本很高,需要了解和熟練掌握分散式系統。為了使用所有開發人員能夠使用任何語言和任何框架輕鬆地構建行動式微服務應用程式,無論是開發新專案還是遷移現有程式碼
Dapr 介紹
Github: https://github.com/dapr/dapr
Dapr是一種可移植的,事件驅動的,無伺服器執行時,用於構建跨雲和邊緣的分散式應用程式。
Distributed Application Runtime. An event-driven, portable runtime for building microservices on cloud and edge.
其中提到了多語言和多開發者框架,我認為這是他選擇的通過通訊共享資訊,即 HTTP
和 GRPC
支援多語言等特性。微軟想通過這個設定一個構建微服務應用的規則。從根本上確立你開發的每一個應用的獨立性。賦能每個開發者,為了使Dapr對於不同的語言更加方便,它還包括針對Go,Java,JavaScript,.NET和Python的語言特定的SDK。這些SDK通過型別化的語言API(而不是呼叫http / gRPC API)公開了Dapr構建塊中的功能,例如儲存狀態,釋出事件或建立actor。這使開發人員可以使用他們選擇的語言編寫無狀態和有狀態功能以及參與者的組合。並且由於這些SDK共享Dapr執行時,您甚至可以獲得跨語言的actor和功能支援!
Dapr還可以與任何開發人員框架整合。例如,在Dapr .NET SDK中,您將找到ASP.NET Core整合,該整合帶來了可響應其他服務的釋出/訂閱事件的狀態路由控制器,從而使ASP.NET Core成為構建微服務Web應用程式的更好框架。
不過需要注意的是Dapr目前正處於Alpha階段, 今天剛釋出了0.2版本。在v1.0穩定版本釋出之前,建議不要用於生產環境。
下面進行一個 QuickStart
環境
- Install Docker(微服務已經離不開容器化了)
- Install Dapr CLI
- Install .Net Core SDK 3.0
在Windows 上通過Powershell 安裝:
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
然後把 c:\dapr 新增到環境變數 PATH
執行dapr命令,檢查輸出是否正常
C:\workshop\Github\dotnet-sdk>dapr --help
__
____/ /___ _____ _____
/ __ / __ '/ __ \/ ___/
/ /_/ / /_/ / /_/ / /
\__,_/\__,_/ .___/_/
/_/
======================================================
A serverless runtime for hyperscale, distributed systems
Usage:
dapr [command]
Available Commands:
help Help about any command
init Setup dapr in Kubernetes or Standalone modes
list List all dapr instances
publish publish an event to multiple consumers
run Launches dapr and your app side by side
send invoke a dapr app with an optional payload
stop Stops a running dapr instance and its associated app
uninstall removes a dapr installation
Flags:
-h, --help help for dapr
--version version for dapr
Use "dapr [command] --help" for more information about a command.
執行初始化(會啟動 docker 容器)
dapr init
Making the jump to hyperspace...
Downloading binaries and setting up components
Success! Dapr is up and running
下載.NET SDk程式碼
https://github.com/dapr/dotnet-sdk ,裡面有.NET Core的多個示例程式碼:
示例 | 描述 |
1. Actor | Demonstrates creating virtual actors that encapsulate code and state. Also see docs in this repo for a tutorial. |
2. ASP.NET Core | Demonstrates ASP.NET Core integration with Dapr by create Controllers and Routes. |
3. gRPC client |
The gRPC client sample shows how to make Dapr calls to publish events, save state, get state and delete state using a gRPC client. |
我們一起來看下ASP.NET Core的Demo;
例子中主 我們使用 Dapr
的互動。Dapr通過 Runtime
- 提供 Dapr API 給多語言呼叫。
- 提供 狀態管理 By state stores
/// <summary>
/// Sample showing Dapr integration with controller.
/// </summary>
[ApiController]
public class SampleController : ControllerBase
{
/// <summary>
/// Gets the account information as specified by the id.
/// </summary>
/// <param name="account">Account information for the id from Dapr state store.</param>
/// <returns>Account information.</returns>
[HttpGet("{account}")]
public ActionResult<Account> Get(StateEntry<Account> account)
{
if (account.Value is null)
{
return this.NotFound();
}
return account.Value;
}
/// <summary>
/// Method for depositing to account as psecified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="stateClient">State client to interact with dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
[Topic("deposit")]
[HttpPost("deposit")]
public async Task<ActionResult<Account>> Deposit(Transaction transaction, [FromServices] StateClient stateClient)
{
var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);
state.Value ??= new Account() { Id = transaction.Id, };
state.Value.Balance += transaction.Amount;
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for withdrawing from account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="stateClient">State client to interact with dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
[Topic("withdraw")]
[HttpPost("withdraw")]
public async Task<ActionResult<Account>> Withdraw(Transaction transaction, [FromServices] StateClient stateClient)
{
var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);
if (state.Value == null)
{
return this.NotFound();
}
state.Value.Balance -= transaction.Amount;
await state.SaveAsync();
return state.Value;
}
}
這裡重點是狀態儲存,即將state
通過 StateClient 儲存在Dapr
中,我們通過狀態轉移在Dapr
裡實現了stateless。
Dapr 執行.NET 應用程式
演示Dapr的服務呼叫,在終端中切換到專案目錄,然後使用dapr啟動應用
C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample>dapr run --app-id routing --app-port 5000 dotnet run
Starting Dapr with id routing. HTTP Port: 61102. gRPC Port: 61103
You're up and running! Both Dapr and your app logs will appear here.
注意: 以上dapr run命令,通過app-id指定了應用的ID,通過app-port指定了應用的埠(webapi預設使用5000作為http埠),後跟dotnet run命名啟動當前專案。可參考Dapr文件服務呼叫
後臺執行的 CLI
命令,這裡是前臺列印的日誌, 注意到 .NET App
在指定的 5000
埠執行,同時還有狀態儲存的 redis
在 6379
埠執行
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="starting Dapr Runtime -- version 0.2.0 -- commit c75b11
1-dirty"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="log level set to: info"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="standalone mode configured"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="dapr id: routing"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="loaded component messagebus (pubsub.redis)"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="loaded component statestore (state.redis)"
== DAPR == time="2019-11-16T18:33:22+08:00" level=info msg="application protocol: http. waiting on port 5000"
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP == Now listening on: http://localhost:5000
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP == Application started. Press Ctrl+C to shut down.
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP == Hosting environment: Development
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP == Content root path: C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample
== DAPR == time="2019-11-16T18:33:31+08:00" level=info msg="application discovered on port 5000"
== DAPR == 2019-11-16 18:33:32.029764 I | redis: connecting to localhost:6379
== DAPR == 2019-11-16 18:33:32.036316 I | redis: connected to localhost:6379 (localAddr: [::1]:61164, remAddr:
[::1]:6379)
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actor runtime started. actor idle timeout: 1h0m0s.
actor scan interval: 30s"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: starting connection attempt to placement se
rvice at localhost:6050"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="http server is running on port 61102"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="gRPC server is running on port 61103"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="local service entry announced"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 917
6.5164ms"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: established connection to placement service
at localhost:6050"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: placement order received: lock"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: placement order received: update"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: placement tables updated"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="actors: placement order received: unlock"
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP == Start processing HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP == Sending HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP == Received HTTP response after 2228.2998000000002ms - OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP == End processing HTTP request after 2257.3405000000002ms - OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP == Start processing HTTP request POST http://localhost:61102/v1.0/state
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP == Sending HTTP request POST http://localhost:61102/v1.0/state
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP == Received HTTP response after 67.46000000000001ms - Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP == End processing HTTP request after 68.0343ms - Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP == Start processing HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP == Sending HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP == Received HTTP response after 5.8247ms - OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP == End processing HTTP request after 6.268400000000001ms - OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP == Start processing HTTP request POST http://localhost:61102/v1.0/state
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP == Sending HTTP request POST http://localhost:61102/v1.0/state
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP == Received HTTP response after 4.5181000000000004ms - Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP == End processing HTTP request after 4.6208ms - Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP == Start processing HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP == Sending HTTP request GET http://localhost:61102/v1.0/state/17
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP == Received HTTP response after 20.2967ms - OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP == End processing HTTP request after 20.691100000000002ms – OK
為了同時實現可移植性和與現有程式碼的輕鬆整合,Dapr通過http或gRPC提供了標準API。Dapr埠可從Dapr啟動日誌中獲取,如以下日誌表示Dapr公開的HTTP埠為61102(通過Dapr也可使用gRPC方式進行服務呼叫)
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="http server is running on port 61102"
== DAPR == time="2019-11-16T18:33:32+08:00" level=info msg="gRPC server is running on port 61103"
我們可通過以下地址來呼叫示例方法,根據Dapr服務呼叫API規範,其代理呼叫規則為:
POST/GET/PUT/DELETE http://localhost:<Dapr埠>/v1.0/invoke/<id>/method/<method-name>
:
直接呼叫:GET http://localhost:5000/17
通過Dapr服務呼叫: GET http://localhost:61102/v1.0/invoke/routing/method/17
注意: Dapr的服務呼叫是有dapr sidecar來實現的,在被呼叫的服務中無需注入任何與dapr相關的程式碼。