.NET 5 with Dapr 初體驗 1 關於Dapr 2 準備工作 3 .NET 5 應用集成Dapr SDK 4 服務調用示例 5 消息發佈及訂閱示例 6 小結 代碼示例

簡介: 分佈式應用運行時Dapr目前已經發布了1.1.0版本,阿里雲也在積極地爲Dapr貢獻代碼和落地實踐。作爲一名開發者,自然也想玩一玩,看看Dapr帶來的新“視”界到底是怎麼樣的。

分佈式應用運行時Dapr目前已經發布了1.1.0版本,阿里雲也在積極地爲Dapr貢獻代碼和落地實踐。作爲一名開發者,自然也想玩一玩,看看Dapr帶來的新“視”界到底是怎麼樣的。

1 關於Dapr

Dapr(Distributed Application Runtime)是一個開源、可移植、事件驅動的運行時。它使開發人員能夠輕鬆地構建運行在雲平臺和邊緣的彈性而微服務化的應用程序,無論是無狀態還是有狀態。Dapr 讓開發人員能夠專注於編寫業務邏輯,而不是解決分佈式系統的挑戰,從而顯著提高生產力並減少開發時間。此外,Dapr 也降低了大部分中小型企業基於微服務架構構建現代雲原生應用的准入門檻。

Dapr 的核心構建模塊 (或者說核心功能)如下:

  • 服務調用: 彈性服務與服務之間(service-to-service)調用可以在遠程服務上啓用方法調用,包括重試,無論遠程服務在受支持的託管環境中運行在何處。
  • 狀態管理:通過對鍵 / 值對的狀態管理,可以很容易編寫長時間運行、高可用性的有狀態服務,以及同一個應用中的無狀態服務。狀態存儲是可插入的,並且可以包括 Azure Cosmos 或 Redis,以及組件路線圖上的其他組件,如 AWS DynamoDB 等。
  • 在服務之間發佈和訂閱消息(Pub/Sub):使事件驅動的架構能夠簡化水平可擴展性,並使其具備故障恢復能力。
  • 事件驅動的資源綁定:資源綁定和觸發器在事件驅動的架構上進一步構建,通過從任何外部資源(如數據庫、隊列、文件系統、blob 存儲、webhooks 等)接收和發送事件,從而實現可擴展性和彈性。例如,你的代碼可以由 Azure EventHub 服務上的消息觸發,並將數據寫入 Azure CosmosDB。
  • 虛擬角色:無狀態和有狀態對象的模式,通過方法和狀態封裝使併發變得簡單。Dapr 在其虛擬角色(Virtual Actors)運行時提供了許多功能,包括併發、狀態、角色激活 / 停用的生命週期管理以及用於喚醒角色的計時器和提醒。
  • 服務之間的分佈式跟蹤:使用 W3C 跟蹤上下文(W3C Trace Context)標準,輕鬆診斷和觀察生產中的服務間調用,並將事件推送到跟蹤和監視系統。

目前Dapr提供瞭如下所示的主流語言的SDK:

更多關於Dapr的介紹不是本文的重點,有興趣的讀者可以移步閱讀:

(1)阿里巴巴的Dapr實踐與探索

(2)Dapr是否會引領雲原生中間件的未來

(3)分佈式運行時 Dapr 知多少

本文的試玩會主要集中在服務調用(service invocation)和 發佈訂閱(pub / sub)上面,並且只會在入門小DEMO的程度,期望值過高的童鞋可以自行學習 或 繞道行走,畢竟我的時間也有限。

2 準備工作

一臺Linux虛擬機

爲了後面的DEMO,在VMware Workstation中準備一個Linux虛擬機環境,這裏我選擇的是CentOS 7.6。

在此虛擬機中設定靜態IP地址(本示例爲 192.168.2.100),關閉防火牆,設定主機名等一系列基本操作。

安裝.NET 5 SDK

這裏我的DEMO是基於local-host部署模式(也可以選擇Kubernetes模式部署,但我沒時間弄),因此給Linux安裝一下.NET 5 SDK,命令如下:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">添加受信源 sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
安裝.NET 5 SDK sudo yum install dotnet-sdk-5.0</pre>

安裝Dapr CLI

官網提示直接在Linux下執行以下命令就可以將Dapr CLI下載到/usr/local/bin目錄下:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash</pre>

不過由於網絡原因,我選擇了直接下載Release來安裝:

(1)到github上下載1.1.0的release壓縮包(dapr_linux_amd64.tar.gz),並將其傳到Linux中。

(2)解壓該壓縮包,並將解壓後的目錄移動到/usr/local/bin目錄下:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">tar -zvxf dapr_linux_amd64.tar.gz</pre>

(3)通過輸入 dapr 來驗證是否安裝成功:

此外,也可以通過 dapr --version 查看Dapr版本:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">CLI version: 1.1.0 Runtime version: 1.1.0</pre>

初始化Dapr

安裝好Dapr CLI之後,就可以在Linux上初始化Dapr了,命令如下:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">dapr init</pre>

這個命令會幫你做一些列的事情,包括但不限於 拉取一波docker鏡像 & 運行一波docker容器,如下圖所示:

可以看到,dapr, redis, zipkin都已經運行起來了。

爲什麼有redis?因爲它會作爲默認的pub/sub中間件爲dapr提供具體的實現能力。

爲什麼會有zipkin?因爲它會作爲默認的tracing中間件爲我們提供鏈路追蹤的能力。

OK,到此爲止,本地的Dapr運行時基礎環境已基本就緒。

3 .NET 5 應用集成Dapr SDK

準備三個.NET WebAPI

這裏我們準備了三個WebAPI項目,分別是訂單服務、購物車服務 以及 商品服務。

具體的代碼可以去github上查看,github地址爲:https://github.com/EdisonChou/EDT.Dapr.Sample

爲所有WebAPI項目添加集成

爲所有項目添加Dapr SDK的nuget包,這裏是 Dapr.AspNetCore 組件。

[圖片上傳中...(image-efe342-1618827344018-7)]

爲所有WebAPI項目註冊Dapr

在StartUp類中,對Dapr Client進行註冊,這裏的AddDapr背後的操作其實就是給IoC容器注入了一個單例的DaprClient對象。

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddDapr();
......
}</pre>

4 服務調用示例

這裏假設CartService要和ProductService進行通信,通過REST獲取商品數據。這裏,就可以藉助Dapr提供的服務間調用的功能進行通信。其工作原理如下圖所示:

這裏使用的方式是通過DaprClient直接InvokeMethod進行服務間的通信,傳遞了兩個重要的參數,一個是依賴服務的app-id(根據你部署時設定的名字來寫),另一個是依賴接口的route。

具體如下代碼所示:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">[ApiController]
[Route("[controller]")] public class CartController : ControllerBase
{ private readonly ILogger<CartController> _logger; private readonly DaprClient _daprClient; public CartController(ILogger<CartController> logger, DaprClient daprClient)
{
_logger = logger;
_daprClient = daprClient;
}

[HttpGet] public async Task<IEnumerable<SKU>> Get()
{
    _logger.LogInformation("[Begin] Query product data from Product Service"); var products = await _daprClient.InvokeMethodAsync<IEnumerable<SKU>> (HttpMethod.Get, "ProductService", "Product");

    _logger.LogInformation($"[End] Query product data from Product Service, data : {products.ToArray().ToString()}"); return products;
}

}</pre>

這裏對應ProductService的接口默認返回一些假數據:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">[ApiController]
[Route("[controller]")] public class ProductController : ControllerBase
{ private static readonly string[] FakeProducts = new[]
{ "SKU1", "SKU2", "SKU3", "SKU4", "SKU5", "SKU6", "SKU7", "SKU8", "SKU9", "SKU10" };

......

[HttpGet] public IEnumerable<SKU> Get()
{
    _logger.LogInformation("[Begin] Query product data."); var rng = new Random(); var result = Enumerable.Range(1, 5).Select(index => new SKU
    {
        Date = DateTime.Now.AddDays(index),
        Index = rng.Next(1, 100),
        Summary = FakeProducts[rng.Next(FakeProducts.Length)]
    })
    .ToArray();

    _logger.LogInformation("[End] Query product data."); return result;
}

}</pre>

然後,將這兩個服務發佈到Linux服務器上,當然,我們要通過dapr來部署,讓.net application和dapr sidecar形成一體。

部署命令如下所示,可以看到我們既要爲.net application指定端口,也要爲dapr sidecar指定端口(這裏主要爲dapr指定了http端口,也可以爲其指定grpc端口)。

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">dapr run --app-id CartService --app-port 5000 --dapr-http-port 5005 -- dotnet EDT.EMall.Cart.API.dll --urls "http://:5000" dapr run --app-id ProductService --app-port 5010 --dapr-http-port 5015 -- dotnet EDT.EMall.Product.API.dll --urls "http://:5010"</pre>

你會發現,當你run成功之後,會看到以下log,其中既有dapr的log,也有.net application的log,雖然他們是兩個應用程序,但是你看到的它們是一體的。

最後,通過swagger來測試一下,結果如下,成功進行了服務調用。

5 消息發佈及訂閱示例

發佈訂閱模式(Publish-Subscribe)是衆所周知且廣泛使用的消息模式。這裏我們假設OrderService的某個接口完成後就發佈一個消息,告知訂閱方有新訂單的事件產生。

在Dapr中其工作原理如下圖所示:

具體代碼示例如下,藉助DaprClient的PublishEvent接口實現消息發佈:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">[ApiController]
[Route("[controller]")] public class OrderController : ControllerBase
{ private const string DaprPubSubName = "pubsub"; private readonly ILogger<OrderController> _logger; private readonly DaprClient _daprClient; public OrderController(ILogger<OrderController> logger, DaprClient daprClient)
{
_logger = logger;
_daprClient = daprClient;
}

[HttpPost] public async Task<Models.Order> Post(OrderDto orderDto)
{
    _logger.LogInformation("[Begin] Create Order."); var order = new Models.Order()
    { // some mapping
        Id = orderDto.Id,
        ProductId = orderDto.ProductId,
        Count = orderDto.Count
    }; // some other logic for order

    var orderStockDto = new OrderStockDto()
    {
        ProductId = orderDto.ProductId,
        Count = orderDto.Count
    }; await _daprClient.PublishEventAsync(DaprPubSubName, "neworder", orderStockDto);

    _logger.LogInformation($"[End] Create Order Finished. Id : {orderStockDto.ProductId}, Count : {orderStockDto.Count}"); return order;
}

}</pre>

假設ProductService作爲訂閱方,需要消費這個事件,並扣減某個商品的庫存。而基於Dapr,我們需要對ProductService添加一點配置:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
......
app.UseCloudEvents(); // 標準化的消息傳遞格式
app.UseEndpoints(endpoints => {
endpoints.MapSubscribeHandler(); // 訂閱消費處理
......
});
}</pre>

然後,在ProductService中添加一個方法/接口 來作爲訂閱處理。

具體代碼示例如下,需要注意的就是:

(1)作爲消息處理接口,需要指定爲HttpPost方式。

(2)需要指定Topic特性,並標註pubsubname 和 事件名。

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">private const string DaprPubSubName = "pubsub";

[HttpPost]
[Topic(DaprPubSubName, "neworder")] public Models.Product SubProductStock(OrderStockDto orderStockDto)
{
_logger.LogInformation($"[Begin] Sub Product Stock, Stock Need : {orderStockDto.Count}."); var product = _productService.GetProductById(orderStockDto.ProductId); if (orderStockDto.Count < 0 || orderStockDto.Count > product.Stock)
{ throw new InvalidOperationException("Invalid Product Count!");
}
product.Stock = product.Stock - orderStockDto.Count;
_productService.SaveProduct(product);

_logger.LogInformation($"[End] Sub Product Stock Finished, Stock Now : {product.Stock}."); return product;

}</pre>

這裏的DaprPubSubName是pubsub,這是因爲Dapr默認的pubsub實現是基於Redis的,而在配置中爲Redis設置的name就是 pubsub,因此對於我們入門的話,就不要去更改,或者和配置中的name保持一致。

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">[root@dapr-lab-server ~]# cat ~/.dapr/components/pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
metadata: - name: redisHost
value: localhost:6379

  • name: redisPassword
    value: ""</pre>

當然,我們也可以將默認的pubsub實現Redis換爲熟悉的RabbitMQ。我們只需要更改上面的yml文件內容如下:

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub-rq
spec:
type: pubsub.rabbitmq
version: v1
metadata: - name: host
value: "amqp://localhost:5672"

  • name: durable
    value: true</pre>

然後,將這兩個服務發佈到Linux服務器上,當然,我們要通過dapr來部署,讓.net application和dapr sidecar形成一體。

<pre style="box-sizing: border-box; margin: 0px 0px 16px; padding: 16px; overflow: auto; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13.6px; overflow-wrap: normal; background-color: rgb(246, 248, 250); border-radius: 3px; line-height: 1.45; position: relative;">dapr run --app-id OrderService --app-port 5020 --dapr-http-port 5025 -- dotnet EDT.EMall.Order.API.dll --urls "http://:5020" dapr run --app-id ProductService --app-port 5010 --dapr-http-port 5015 -- dotnet EDT.EMall.Product.API.dll --urls "http://:5010"</pre>

run成功後,通過 dapr list 查看,可以看到三個服務都已經啓動起來了,它們是三個由.net application + dapr sidecar 組成的“合體應用”。

最後,我們通過swagger來測試一下,測試結果如下圖所示:

(1)OrderService:

(2)ProductService:

這裏的99其實是假總庫存100 - 消息傳遞過來的商品數量得到的,具體可以參考代碼示例。

6 小結

本文總結了我試玩Dapr的一些經過,包括Dapr的Local環境搭建、.NET 5 Application與Dapr的集成 和 兩個具體場景的小DEMO(服務調用 和 Pub/Sub)。

這裏藉助知乎上 **iyacontrol **童鞋的評論(來源:https://www.zhihu.com/question/351298264),作爲結尾:

Dapr 本身是一種 Sidecar 模式(雖然Dapr也提供了SDK,但是個人認爲這並不是Dapr以後的發展方向)。Sidecar 模式的意義在於, 解耦了基礎設施和核心業務

簡單來看,Dapr的意義在於:

  • 對於小公司,甚至沒有基礎架構和中間件團隊的公司,Dapr 提供了開箱即用的基礎設施功能,可以讓小公司輕鬆構建彈性,分佈式應用
  • 對於中等單位,具備一定的基礎架構能力,在使用Dapr的過程中,可能Dapr並不能完全滿足需求,那麼也可以在Dapr框架體系下,花費較小的成本進行自定義擴展
  • 對於大公司,Dapr 提供了一種思路。相信基礎架構團隊會越來越傾向於通過交付Sidecar的形式來提供基礎設施

長遠來看,Dapr背後的架構模式是符合未來架構趨勢(多運行時架構)和雲原生髮展趨勢的

代碼示例

github:https://github.com/EdisonChou/EDT.Dapr.Sample

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章