Spiga

云原生电商微服务实战3:Asprise和Dapr集成

2024-09-28 13:27:01

一、云原生介绍

  1. 概念

    什么是云原生:云原生是一种软件开发的方法论,通俗来说就是在云计算环境中构建和运行应用程序 云计算的特点:弹性、可扩展、高可用 云原生关键点:

    • 容器化
    • 微服务
    • 动态管理
    • CI/CD
    • 弹性(容错机制)
    • 服务网格
    • 容器编排工具
    • 监控平台(指标、链路、日志)
  2. 云原生开发与微服务开发 理念(云原生:云计算为基础;传统:固定的硬件) 架构(云原生:微服务、Serverless;传统:单机架构) 开发流程(云原生:CI/CD;传统:瀑布模型(没有自动化)) 基础设施(云原生:通过代码管理依赖资源、自动配置;传统:事先准备、手动配置) 服务发现通信(云原生:环境中自带服务发现;传统:Consul、自己实现、提前准备、依赖硬编码、配置文件) 监控和日志(云原生:环境中准备好了各种监控、日志、链路追踪;传统:过于依赖外部组件) 资源利用率(云原生:动态调整资源;传统:资源分配不灵活)

  3. 开发环境和生产环境

    云原生概要已经提出很久了,k8s可以说就是上就是一个云原生环境。

    我在2019年的时候就开始带团队成员来是公司的微服务转型,那个时候都是自己手撸k8s环境,各种中间件集群也是自己搭建的。

    那个时候云原生环境确实不好,虽然各大云平台都提供直接的paas产品,但很多收费都较贵,最终还是自己搭建。

    更重要的时候本地开发时虽然安装的docker版本,提供了mini k8s环境,但是开发和部署的环境还是不一样的。

二、Asprise介绍

如果能有一个让开发和部署都是云原生的,开发的时候不需要自己去安装mysql,redis;同时开发的时候也不需要知道最终部署的是阿里云,还是微软云。只要开发环境是基于云原生的,开发环境拥有云原生的特性,比如监控、日志、链路追踪。部署的时候只需要提供对应的云产品的产品就能让系统跑起来,这种跑起来的方式,可能就是提供一个连接字符串就可以了。如果有这样的环境,那云原生开发将变得简单很多。

对的,这就是我们当前的明星出场的时候了。

.NET Aspire是NET 8.0 LTS提出的一套开发工具,它的作用就是构建和运行云原生应用程序。

特点:

  1. Dashboard - 中心化的应用监控和探查:F5 启动 .NET Aspire可显示服务的统一视图以及它们的日志、指标和跟踪。

  2. 组件 Component:开发Asprise应用时,我们无需再在本地安装需要用到的组件环境了,比如mysql、redis。只需要为启动的服务体提供配置,告诉Asprise该服务需要用到MongoDb、RabbitMQ等。

  3. Azure 专用组件:这里我们用不到,但还是介绍一下,Asprise提供了专用的Azure组件,可以将应用直接部署到Azure,并且本地开发也能直接只用Azure云服务,比如Blob、Cosmos DB等

  4. 服务发现:使用Asprise后,开发人员不需要在配置服务发现,而且每一个服务Asprise还提供一个代理,这样我们启动某个服务的时候可以直接生成集群,跨服务的请求通过代理来访问集群

  5. 生成k8s部署清单,当然这个目前还存在bug,但生成出来的清单稍做修改基本就可以用了。

    Asprise它很新,每天都在变化,目前我只研究到这里。还有很多特点我可能还不知道,期待Asprise更好的发展。

三、创建Asprise项目

  1. 环境:.NET 8.0、Docker桌面版、VS 2022最新

  2. 创建aspire文件夹,再创建名为DDM.DHT.AppHost的.NET Asprise应用主机项目。添加下列引用

        <PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
        <PackageReference Include="Aspire.Hosting.MongoDB" Version="9.2.0" />
        <PackageReference Include="Aspire.Hosting.MySql" Version="9.2.0" />
        <PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.2.0" />
        <PackageReference Include="Aspire.Hosting.Redis" Version="9.2.0" />
    
  3. 添加DDM.DHT.UserService.HttpApi和DDM.DHT.UserService.MigrationWorker项目引用

  4. 在Program.cs中添加下列代码,用于创建组件:

    var mysql = builder.AddMySql("dht-mysql")
        .WithPhpMyAdmin(resourceBuilder => resourceBuilder.WithLifetime(ContainerLifetime.Persistent))
        .WithLifetime(ContainerLifetime.Persistent);
    
    var mongo = builder.AddMongoDB("dht-mongo")
        .WithMongoExpress()
        .WithLifetime(ContainerLifetime.Persistent);
    
    var redis = builder.AddRedis("dht-redis")
        .WithRedisInsight(resourceBuilder => resourceBuilder.WithLifetime(ContainerLifetime.Persistent))
        .WithLifetime(ContainerLifetime.Persistent);
    
    var username = builder.AddParameter("username", secret: true);
    var password = builder.AddParameter("password", secret: true);
    var rabbitmq = builder.AddRabbitMQ("rabbitmq", username, password, 5672)
        .WithManagementPlugin()
        .WithLifetime(ContainerLifetime.Persistent);
    

    上面代码分别创建了mysql、mongodb、redis和rabbit MQ组件,开发者本地不需要安装这些。

  5. 创建UserServiceBuilder.cs文件,代码如下:

    public static class UserServiceBuilder
    {
        private const string MasterDb = nameof(MasterDb);
        private const string SlaveDb = nameof(SlaveDb);
    
        public static void AddUserService(this IDistributedApplicationBuilder builder,
            IResourceBuilder<MySqlServerResource> mysql,
            IResourceBuilder<RedisResource> redis,
            IResourceBuilder<RabbitMQServerResource> rabbitmq)
        {
            var db = mysql.AddDatabase("dht-user");
    
            var appId = builder.Configuration["AppId:UserService"];
            ArgumentNullException.ThrowIfNull(appId);
    
            var migration = builder.AddProject<DDM_DHT_UserService_MigrationWorker>("user-service-migration")
                .WithReference(db, MasterDb)
                .WaitFor(mysql);
    
            builder.AddProject<DDM_DHT_UserService_HttpApi>(appId)
                .WithReference(db, MasterDb)
                .WithReference(db, SlaveDb)
                .WaitFor(mysql)
                .WithReference(redis)
                .WaitFor(redis)
                .WaitFor(rabbitmq)
                .WaitForCompletion(migration);
        }
    }
    

    这个文件是用户微服务的配置,以后每个微服务都单独创建一个Builder类来配置。

    文件中我们可以看到,参数分别传入了mysql、redis、rabbitmq,这是用户服务需要用到的中间件。

    创建的migration对象是DDM.DHT.UserService.MigrationWorker项目的实例,这是用来在Asprise的mysql组件中,自动去实现迁移。

    最后就是builder的链式代码,分别添加了mysql写库、读库、redis、rabbitmq等资源后,再自动启动项目。

  6. 在Program.cs添加UserServiceBuilder方法的调用

    builder.AddUserService(mysql, redis, rabbitmq);
    
  7. 设置DDM.DHT.AppHost为启动项目

  8. 创建名为DDM.DHT.ServiceDefaults的Aspire服务默认值项目,这个项目创建后什么都不用管,也不用写代码

  9. 让DDM.DHT.UserService.HttpApi和DDM.DHT.UserService.MigrationWorker引用Aspire服务默认值项目

    我们看项目的Program类,里面有代码 builder.AddServiceDefaults(); 这个就是Aspire服务默认值项目提供的。

  10. 集成下一个主题Dapr后,就可以使用AppHost F5启动项目了。

四、集成Dapr

  1. Dapr 是一个便携的事件驱动运行时环境,帮助开发者轻松构建在云端和边缘运行的高可用、无状态和有状态应用程序,并支持多种编程语言和开发框架。通过利用 sidecar 架构的优势,Dapr 帮助您解决构建微服务时的挑战,并保持代码与平台无关。

  2. 安装:

    • Dapr CLI运行时,我们项目安装的是 Dapr CLI v1.15.1.msi

      https://github.com/dapr/cli/releases

    • C盘会自动有个dapr的文件夹

    • dapr init,执行后可以看到docker里面出现4个容器

    • dapr --version 验证一下

    • 双击dapr.exe启动

  3. User微服务集成Dapr

    • 在DDM.DHT.AppHost项目添加文件夹DaprComponents,我们先添加一个configuration.yaml文件

      apiVersion: dapr.io/v1alpha1
      kind: Component
      metadata:
        name: redis-config
      spec:
        type: configuration.redis
        version: v1
        metadata:
          - name: redisHost
            value: localhost:6379
          - name: redisPassword
            value: ""
      

      这个文件启动一个Dapr的配置构建块,使用redie来存储配置

    • 在Program类,添加Dapr文件目录

      var daprSidecarOptions = new DaprSidecarOptions
      {
          ResourcesPaths = ["./DaprComponents"]
      };
      
    • 在UserServiceBuilder类启动User项目时,启动Dapr

              builder.AddProject<DDM_DHT_UserService_HttpApi>(appId)
                  .WithReference(db, MasterDb)
                  .WithReference(db, SlaveDb)
                  .WaitFor(mysql)
                  .WithReference(redis)
                  .WaitFor(redis)
                  .WithEnvironment("APP_API_TOKEN", appApiToken)
                  .WithDaprSidecar(daprSidecarOptions)
                  .WaitFor(rabbitmq)
                  .WaitForCompletion(migration);
      

      这里的参数daprSidecarOptions是通过方法参数传进来的

      builder.AddUserService(mysql, redis, rabbitmq, appApiToken, daprSidecarOptions);
      

五、项目演示

  1. 项目启动展示

  • 图中我们可以看到,Asprise为不仅项目启动了mongdb、mysql、redis和rabbit MQ中间件,还同时启动了phpmyadmin、redis-insight等管理工具。
  • 同时启动了user-service-httpapi项目,生成了https和http两个访问端口。
  • 并且还生成了该api项目的 dapr-cli代理,可以用这个代理来访问api集群,只不过我们目前只生成了一个userapi项目。
  • 项目user-service-migration的状态是Finished,说明启动api项目时同时也启动了数据库迁移项目,并且在完成项目迁移后关闭了该项目,所以状态是Finished。
  1. 点击api项目5196端口访问进去,输入/scalar/v1。我们可以看到api接口页面,这个页面是取代swagger的。.net 9 swagger ui不支持了,本项目使用来代替。我们已经在DDM.DHT.HttpApi.Common功能项目集成Scalar了,下图是访问效果。

    我们再选择创建用户,点击右边的Test按钮,输入json传入参数,即可调试接口

  2. 项目左侧菜单,我们可以看到还有控制台、结构化、跟踪、指标等页面,这些是Asprise为项目生成的查看日志、链路追踪、系统指标等功能,这些功能无需开发者再去实现了,让本地开发变得更容易。

上图部分数据就是显示了调用创建用户接口产生的数据。