七叶笔记 » golang编程 » Dapr牵手.NET学习笔记:状态管理之docker-compose发布

Dapr牵手.NET学习笔记:状态管理之docker-compose发布

说明:为了给出demo的全貌,这篇有点长,如果有上一篇的基础,会更容易阅读一些。

在分布式应用,有状态服务是常态,特别是多副本应用,就需要共用缓存来解决数据统一的状况,所以dapr也把状态管理做成一个标准组件。

下面通过 docker -compose来发布OrderSystem项目和PaymentSystem项目,他们分别有自己的状态数据,并且测试它们之间的访问性。

下面是项目的目录结构,components文件是dapr组件配置文件夹, B2C 是docker-compose的文件夹

OrderSystem项目

HomeController.cs

 using  Microsoft . AspNet Core.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace OrderSystem.Controllers;
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
     private  readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory _clientFactory;
    private readonly string? _payUrl;
    private readonly string _stateUrl;
    public HomeController(ILogger<HomeController> logger, IHttpClientFactory clientFactory, IConfiguration configuration)
    {
        _stateUrl = configuration.GetSection("StateUrl").Value;
        _payUrl = configuration.GetSection("payurl").Value;
        _clientFactory = clientFactory;
        _logger = logger;
    }
    [HttpGet("/order")]
    public async Task<IActionResult> Order()
    {
        try
        {
            _logger.LogInformation($"下单开始");
            await Task.Delay(400);
            _logger.LogInformation($"订单完成   调用支付系统");
            var client = _clientFactory.CreateClient();
            var content = await client.GetStringAsync(_payUrl);
            return new  Json Result(new { order_result = "订单成功", pay_result = content });
        }
        catch ( Exception  exc)
        {
            _logger.LogCritical(exc, exc.Message);
            return new JsonResult(new { order_result = "订单成功,支付失败", message = exc.Message });
        }
    }

    [HttpPost("/writekeys")]
    public async Task<IActionResult> WriteKeys([FromBody] KeyEntity[] keys)
    {
        var client = _clientFactory.CreateClient();
        var jsonContent = System.Text.Json.JsonSerializer.Serialize(keys);
        var content = new StringContent(jsonContent);
        var response = await client.PostAsync(_stateUrl, content);
        return Ok(await response.Content.ReadAsStringAsync());
    }

    [HttpGet("/readekey/{key}")]
    public async Task<IActionResult> ReadKey(string key)
    {
        var client = _clientFactory.CreateClient();
        var response = await client.GetAsync($"{_stateUrl}/{key}");
        return new JsonResult(new { key = await response.Content.ReadAsStringAsync(), host = Dns.GetHostName() });
    }
    [HttpPost("/readekeys")]
    public async Task<IActionResult> ReadKeys([FromBody] string[] keys)
    {
        var client = _clientFactory.CreateClient();
        var jsonContent = System.Text.Json.JsonSerializer.Serialize(keys);
        var content = new StringContent(jsonContent);
        var response = await client.PostAsync($"{_stateUrl}/bulk", content);
        return Ok(await response.Content.ReadAsStringAsync());
    }

    [HttpDelete("/deletekey/{key}")]
    public async Task<IActionResult> DeleteData(string key)
    {
        var client = _clientFactory.CreateClient();
        var response = await client.DeleteAsync($"{_stateUrl}/{key}");
        return Ok(await response.Content.ReadAsStringAsync());
    }
}
public class KeyEntity
{
    public string Key { get; set; }
    public string Value { get; set; }
}  

appsettings.json

 {
  "Urls": "#34;,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "PayUrl": "#34;,
  "StateUrl": "#34;
}  

OrderSystem项目的Dockerfile

 FROM mcr.microsoft.com/ dotnet /aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
 COPY  ["/OrderSystem/OrderSystem.csproj", "OrderSystem/"]
RUN dotnet restore "OrderSystem/OrderSystem.csproj"
COPY . .
WORKDIR "/src/OrderSystem"
RUN dotnet build "OrderSystem.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "OrderSystem.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OrderSystem.dll"]  

PaymentSystem项目

HomeControllers.cs与OrderSystem的状态代码是一样的,这里就不占篇幅。

PaymentSystem项目的appsettings.json

 {
  "Urls": "#34;,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "StateUrl": "#34;
}  

PaymentSystem项目的Dockerfile

 FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["/PaymentSystem/PaymentSystem.csproj", "PaymentSystem/"]
RUN dotnet restore "PaymentSystem/PaymentSystem.csproj"
COPY . .
WORKDIR "/src/PaymentSystem"
RUN dotnet build "PaymentSystem.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "PaymentSystem.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PaymentSystem.dll"]  

OrderSystem项目和PaymentSystem项目的Dockerfile中的路径要和docker-compose.yml中的设置有关,所以分享出来,以供参考。

B2C

docker-compose.yml,这里定义了一个b2c-dapr,作为互通网络,打通应用与 redis 等服务的通道。其中ordersystem对宿主机的端口还是3500,paymentsystem对宿主机的端口分别是3601,3602

 version: '3.4'

services:
  #┌────────────────────────────────┐
  #│ ordersystem app + Dapr sidecar │
  #└────────────────────────────────┘
  ordersystem:
    image: ${DOCKER_REGISTRY-}ordersystem
    depends_on:
      - redis
      - placement
    build:
      context: ../
      dockerfile: /OrderSystem/Dockerfile
    ports:
      - "3500:3500"
    volumes:   
      - ../OrderSystem:/OrderSystem  
    networks:
      - b2c-dapr
  ordersystem-dapr:
     image: "daprio/daprd:latest"
     command: [ "./daprd", "-app-id", "order", "-app-port", "80","-placement-host-address", "placement:50006","-components-path","/components"]
     build:
       context: ../
     depends_on:
       - ordersystem
     network_mode: "service:ordersystem"
     volumes:   
      - ../components:/components  
  
  #┌───────────────────────────────────┐
  #│ paymentsystem1 app + Dapr sidecar │
  #└───────────────────────────────────┘  
  paymentsystem1:
    image: ${DOCKER_REGISTRY-}paymentsystem
    build:
      context: ../
      dockerfile: /PaymentSystem/Dockerfile
    ports:
      - "3601:3500"
    volumes:   
      - ../PaymentSystem:/PaymentSystem      
    networks:
      - b2c-dapr      
  paymentsystem1-dapr:
     image: "daprio/daprd:latest"
     command: [ "./daprd", "-app-id", "pay", "-app-port", "80","-placement-host-address", "placement:50006","-components-path","/components" ]
     build:
       context: ../
     depends_on:
       - paymentsystem1
     network_mode: "service:paymentsystem1"
     volumes:   
      - ../components:/components 
 
  #┌───────────────────────────────────┐
  #│ paymentsystem2 app + Dapr sidecar │
  #└───────────────────────────────────┘   
  paymentsystem2:
    image: ${DOCKER_REGISTRY-}paymentsystem
    build:
      context: ../
      dockerfile: /PaymentSystem/Dockerfile
    volumes:   
      - ../PaymentSystem:/PaymentSystem            
    ports:
      - "3602:3500"
    networks:
      - b2c-dapr      
  paymentsystem2-dapr:
     image: "daprio/daprd:latest"
     command: [ "./daprd", "-app-id", "pay", "-app-port", "80" ,"-placement-host-address", "placement:50006","-components-path","/components"]
     build:
       context: ../
     depends_on:
       - paymentsystem2
     network_mode: "service:paymentsystem2"
     volumes:   
      - ../components:/components       

  #┌────────────────────────┐
  #│ Dapr placement service │
  #└────────────────────────┘  
  placement:
    image: "daprio/dapr"
    command: ["./placement", "-port", "50006"]
    ports:
      - "50006:50006"
    networks:
      - b2c-dapr

  #┌───────────────────┐
  #│ Redis state store │
  #└───────────────────┘  
  redis:
    image: "redis:latest"
    ports:
      - "6380:6379"
    networks:
      - b2c-dapr
 networks :
    b2c-dapr:  

Dapr组件配置

statestore.yaml,其中value的值要改成redis,而不是localhost,这里要连接同一个网络里的redis服务

 apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"  

启动docker-compose

启动后各服务的状态

测试:

对order服务进行设置状态,这里调用的是dpar服务调用对外的接口,3500是order服务sidecar对外端口

同理调用order服务的状态数据,返回设置的值

接下来测试从pay服务中调用order服务的状态,返回结果为空,这里的地址为: localhost:3601/v1.0/invoke/ pay /method/readekey/USER00001

下面换成从pay1服务的3601中访问order服务的状态,结果有值,url为 localhost:3601/v1.0/invoke/ order /method/readekey/USER00001,根本原因可能你想到了,这本质上调的还是order服务的接口,只不过是利用pay1的sidecar调用order的sidecar实现的,因为服务调用是通着,所以能调用到,这里可以通过查看返回的host值证明。

接下来测试pay服务,两个副本,访问状态数据的case。下面是通过3601设置状态。

通过pay1的sidecar对外端口访问,url是 localhost:3601/v1.0/invoke/pay/method/readekey/PAY00001

通过pay2的sidecar对外端口访问,url是 localhost:3602/v1.0/invoke/pay/method/readekey/PAY00001

可见状态数据很好的被隔了,如果跨服务访问状态数据,可以通过服务开放api来实现。

最后看一眼redis数据吧,清晰的展示了服务和数据的关系。

相关文章