從零開始,使用Dapr簡化微服務

福祿網路研發團隊發表於2021-11-26

序言

現有的微服務模式需要再業務程式碼中整合大量基礎設施模組,比如註冊中心,服務發現,服務呼叫鏈路追蹤,請求熔斷,重試限流等等,使得系統過於臃腫重量級。

Dapr作為新一代微服務模式,使用sidecar模式,簡化了現有微服務系統程式碼,將基礎設施層以sidecar模式分離,使得開發人員更集中於業務邏輯編寫。

本文以net6和dapr1.5為基礎,搭建一個dapr的簡單使用示例。

1、安裝Docker

Dapr的執行依賴於Docker環境。

作為學習環境,使用Centos 7系統安裝Docker。

安裝Docker推薦使用daocloud一鍵安裝命令:

curl -sSL https://get.daocloud.io/docker | sh

安裝完成後執行命令:

[root@localhost ~]# docker -v
Docker version 20.10.11, build dea9396

顯示對應的Docker版本即安裝成功。

2、安裝Dapr CLI

官方解釋:Dapr CLI 是您用於各種 Dapr 相關任務的主要工具。 您可以使用它來執行一個帶有Dapr sidecar的應用程式, 以及檢視sidecar日誌、列出執行中的服務、執行 Dapr 儀表板。

下載Dapr CLI

wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash

驗證安裝情況

dapr -v

輸出以下內容即安裝成功。

CLI version: 1.5.0
Runtime version: 1.5.0

由於國內網路問題,使用官方的Dapr安裝方法一般會遇到各種問題,因此把dapr下載下來,通過指令碼進行安裝。

修改hosts檔案

vi /etc/hosts
140.82.114.4 github.com  
199.232.69.194 github.global.ssl.fastly.net
140.82.114.9 codeload.github.com

重新整理快取

yum install -y nscd
service nscd restart

首先需要安裝Git,然後執行以下命令:

git clone -v https://gitee.com/Two-Twoone/dapr-installer.git
cd dapr-installer/
./install.sh

雖然還是很慢,但是總比下不了好多了。

上面命令啟動了幾個容器,執行下列操作來驗證:

[root@localhost dapr-installer]# docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
CONTAINER ID   NAMES            PORTS
a0565f609846   dapr_placement   0.0.0.0:50005->50005/tcp, :::50005->50005/tcp
c392f5cf7a18   dapr_redis       0.0.0.0:6379->6379/tcp, :::6379->6379/tcp
2ee68c450b29   dapr_zipkin      9410/tcp, 0.0.0.0:9411->9411/tcp, :::9411->9411/tcp

3、安裝Net6 SDK

 rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
 yum update
 yum install dotnet-sdk-6.0

4、建立應用程式

建立2個專案分別為 product,cart 引用Dapr

dotnet add package Dapr.AspNetCore

Program.cs中對 的 AddDapr 呼叫將 DaprClient 類註冊到 ASP.NET Core注入系統。 註冊客戶端後,現在可以將 的例項注入服務程式碼, DaprClient 以與 Dapr sidecar、構建基塊和元件進行通訊。

builder.Services.AddControllers().AddDapr();

4.1、服務呼叫

在微服務系統中,服務與服務間的呼叫必不可少,難點主要集中在服務所在位置,發生錯誤時如何重試,負載均衡等問題。

Dapr中使用sidecar 作為服務的反向代理模組來解決這些問題。

prodcut專案增加下列程式碼

    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private ILogger<ProductController> _logger;

        public ProductController(ILogger<ProductController> logger)
        {
            _logger = logger;
        }
        private static readonly List<string> products = new List<string> { "aa", "bb", "cc", "dd", "ee", "ff", "gg", "hh", "ii", "jj", "kk", "ll", "mm", "nn" };
        [HttpGet]
        public  ActionResult Get()
        {
            _logger.LogInformation($"呼叫了獲取商品方法");
            string[] temps = new string[5];
            for (int i = 0; i < 5; i++)
            {
                Random random = new Random(Guid.NewGuid().GetHashCode());
                temps[i] = products[random.Next(0, products.Count - 1)];
            }
            return Ok( temps);
        }
    }
# 啟動Product 專案
dapr run --app-id ProductDemo --app-port 5010 --dapr-http-port 7015 -- dotnet /root/www/product/Dapr.Product.Sample.dll --urls "http://*:5010"

cart專案增加下列程式碼,dapr支援http,grpc呼叫方式,這裡以常用的webapi為例,使用http方式呼叫。

InvokeMethodAsync方法中appid對應的就是dapr run 中的appid,無需關係呼叫地址。

    [Route("api/[controller]")]
    [ApiController]
    public class CartController : ControllerBase
    {
        private  readonly DaprClient _daprClient;
        private  readonly ILogger<CartController> _logger;
        public CartController(DaprClient daprClient, ILogger<CartController> logger)
        {
            _daprClient = daprClient;
            _logger = logger;
        }

        [HttpGet]
        [Route("GetProducts")]
        public async Task<IActionResult> GetProducts()
        {
            _logger.LogInformation($" Cart 獲取商品");
            var products = await _daprClient.InvokeMethodAsync<List<string>>(HttpMethod.Get, "ProductDemo", "/api/Product/GetAll");
            return Ok(products);
        }
    }

將程式上傳到linux伺服器,執行程式

# 啟動 Cart 專案
dapr run --app-id CartDemo --app-port 5020 --dapr-http-port 7025 -- dotnet /root/www/cart/Dapr.Cart.Sample.dll --urls "http://*:5020"

呼叫介面,可以看到Cart專案幾乎沒有程式碼入侵就實現了介面呼叫。

[root@localhost ~]# curl -X 'GET'   'http://192.168.2.110:5020/api/Cart/GetProducts'
["aa","bb","cc","dd","ee","ff","gg","hh","ii","jj","kk","ll","mm","nn"]

Dapr內部使用了mDns進行了服務註冊發現和負載均衡,部署多個product後呼叫,可以看到輪詢呼叫效果。

在自承載模式下,Dapr 使用 mDNS 查詢它。在 Kubernetes 模式下執行時,Kubernetes DNS 服務確定地址。

IxIDLq.png

在呼叫失敗和瞬態錯誤的情況下,服務呼叫會執行自動重試,Dapr 預設是開啟了重試,所以介面不支援冪等是十分危險的行為。

4.2、釋出訂閱

​ 釋出訂閱模式,主要是用於微服務間基於訊息進行相互通訊。你可能也會說,這也要拿出來說,我搞個RabbitMQ/Kafka就是了,

原來我們都會根據使用的元件引入不同的sdk,不同的訊息佇列監聽、消費模式還不一樣。

​ Dapr 提供了一個構建基塊,可顯著簡化實現釋出/訂閱功能,從而和底層基礎設施解耦,編寫業務邏輯時不需要關心是什麼訊息佇列。

再Program中新增發布訂閱支援

        app.UseCloudEvents();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapSubscribeHandler();
        });

訂閱訊息,使用Topic特性,傳遞pubsub和主題名稱

        [Topic("pubsub", "newUser")]
        public ActionResult subUserInfo(UserInfo us)
        {
            _logger.LogInformation($"接收到訂閱訊息 id:{us.id} name:{us.name},age:{us.age},sex:{us.sex}");
            return Ok("處理完畢");
        }

釋出訊息,使用dapr公開方法PublishEventAsync,傳遞pubsub和主題名稱,以及訊息體

    	[HttpPost]
        public async Task<IActionResult> PubUserInfo(UserInfo us)
        {
            await _daprClient.PublishEventAsync("pubsub", "newUser", us);
            return Ok();
        }

訊息釋出訂閱元件支援RabbitMQ,Redis,Kafka等。

4.3、狀態管理

​ Dapr 預設使用 Redis 作為狀態儲存。它也支援MongoDB,PostgreSQL,SQL Server等。

它不會對上層暴露底層用的那個中介軟體,也就是說在不同環境下可以使用同一套程式碼來使用不同的中介軟體。

       [HttpPost]
       [Route("SaveUserList")]
        public async Task<IActionResult> SaveUserList()
        {
            var temps = new List<UserInfo>
            {
                new UserInfo("小紅",1,true,Guid.NewGuid().ToString()),
                new UserInfo("小黃",1,true,Guid.NewGuid().ToString()),
                new UserInfo("小藍",1,true,Guid.NewGuid().ToString())
            };
          	await _daprClient.SaveStateAsync("statestore", "UserList", temps);
            return Ok(1);
        }
        [HttpGet]
        [Route("GetUserList")]
        public async Task<IActionResult> GetUserList()
        {
            var list = await _daprClient.GetStateAsync<List<UserInfo>>("statestore", "UserList");
            return Ok(list);
        }
      	[HttpGet]
        [Route("DeleteUserList")]
        public async Task<IActionResult> DeleteUserList()
        {
            await _daprClient.DeleteStateAsync("statestore", "UserList");
            return Ok(1);
        }

        public record UserInfo(string name, int age, bool sex, string id);

4.4、鏈路追蹤

傳統微服務中,要實現鏈路追蹤,對程式碼的侵入強。

Dapr 在 Sidecar 中新增了一個 http/grpc中介軟體。攔截所有應用程式流量,並自動注入關聯 ID 以跟蹤分散式事務。

使用 Zipkin 協議進行分散式跟蹤,無需程式碼檢測,使用可配置的跟蹤級別自動跟蹤所有流量。

IxIywV.png

IxIfSJ.png

5、總結

​ 本文只是對Dapr做了一個簡單示例,對各個元件具體的實現原理沒有做過多深入講解。

​ Dapr區別於傳統微服務框架最大的優點就是Sidecar 。以前的微服務框架都需要程式碼專案引用微服務相關的一些類庫,無論是服務註冊發現、熔斷、配置等都是要呼叫對應類庫實現,這些類庫是執行在微服務的程式中的,因此這些類庫都需要使用和業務程式碼一樣(或者相容)的語言來開發,因此是比較重的。

​ 而Sidecar 這種模式,把“註冊發現、熔斷、配置”等這些微服務的功能都剝離到一個和業務程式碼的程式相伴而行的獨立程式中,業務程式碼通過http或者grpc等方式和這個Sidecar 程式通訊來完成微服務的相關服務的呼叫。

​ 顯而易見,在Sidecar 這種模式中,業務程式碼中只有極少數和Sidecar 程式通訊的程式碼,因此非常輕量級。這樣Sidecar 程式中的服務可以獨立升級,模組可以自由組合,不會干擾業務程式碼。同時由於Sidecar 的程式是獨立的程式,業務程式碼和Sidecar 程式通訊是採用http、grpc這樣語言無關的協議,因此業務程式碼可以採用任何語言來進行開發。

福祿·研發中心 熊迪

相關文章