当前位置: 欣欣网 > 码农

.NET Aspire 应用程序剖析

2024-07-07码农


添加组件很容易,因为 Aspire 具有成熟的编排器功能,例如服务发现。启动和调试应用程序也很容易。您所要做的就是启动 Aspire 主机应用程序,就像启动和调试任何标准 .NET 项目一样。单击 IDE 中的 「Run」( 运行 )按钮或从 CLI 执行命令将启动 .NET Aspire 主机及其编排的所有应用程序。您可以在其中任何一个中放置断点,它们将被命中。dotnet run

今天,我们将研究基于 .NET Aspire 启动项目模板的基本 .NET Aspire 应用程序。

Aspire 入门

如果以前未使用过 .NET Aspire,则可以按照 Microsoft 提供的官方文档中的说明创建第一个应用程序。假设我们基于 Aspire Starter Project 模板创建了一个新项目,并将其命名为 AspireApp 。这就是我们最终会得到的:

我们可以在此处找到我们将要介绍的项目的完整示例。

此设置包括以下应用程序:

  • AspireApp.Web,这是一个 Blazor Web 应用程序,其用户界面可从外部 REST API 终结点拉取数据。

  • AspireApp.ApiService,这是 Blazor 应用程序从中提取数据的 REST API 应用程序。

  • AspireApp.AppHost,这是协调其他两个应用程序的 Aspire 业务流程协调程序。

  • 还有一个名为的类库,它为 .NET Aspire 编排的应用程序提供一些共享功能。AspireApp.ServiceDefaults

    接下来,让我们看一下编排是如何发生的。

    使用 .NET Aspire 进行编排

    如果我们启动 playground 并等待应用程序构建,我们将能够看到如下所示的仪表板:

    此仪表板向我们展示了作为编排分布式应用程序的一部分正在运行哪些服务。仪表板包含每个服务的以下信息:

  • 服务类型

  • 在 Aspire 环境上下文中使用的唯一名称

  • 状态,告诉我们服务是否正在运行

  • Source,它指向表示服务的项目

  • 服务终结点(如果服务有一个)The service endpoint (if the service has one)

  • 服务日志

  • 服务详情

  • 我们知道上述应用程序正在运行。首先,这是仪表板上列下的值告诉我们的。其次,如果我们通过单击**「控制台**」选项卡打开任一应用程序的日志,我们应该期望它看起来像这样:State

    如果我们返回到**「资源**」选项卡,我们将能够单击任何应用程序的终结点 URL,它将带我们到与终结点关联的地址。因此,让我们单击 webfrontend 的端点:

    这应该会打开一个 Blazor UI 应用程序,我们可以在其中导航到该选项卡。此选项卡包含 Blazor 应用程序从 API 检索的数据。它应该看起来与此类似(尽管确切的值可能会有所不同):Weather

    因此,这就是我们如何让 UI 和 API 协同工作。Aspire 允许我们查看这两个应用程序并导航到它们相应的地址。但远不止于此。在深入研究其项目结构之前,让我们快速总结一下 Aspire 的好处。

    .NET Aspire 的优点

    没有什么能阻止我们在没有任何编排器的情况下让 UI 应用程序与 API 应用程序进行通信。但是,业务流程协调程序(如 NET Aspire)使此过程变得更加容易。以下是它的一些好处:

    服务发现

    如果我们想让 UI 应用程序与 API 通信,我们需要知道 API 的地址。但是,如果我们在负载均衡器或反向代理后面运行 API,该怎么办?如果 API 应用程序部署到完全不同的服务器怎么办?

    在这些情况下,应用程序的地址将发生变化。如果没有编排器,就很难跟踪它。.NET Aspire 解决了这个问题,我们很快就会看到。

    .NET Aspire 业务流程协调程序保留应用程序的实际地址。应用程序本身不需要知道此信息。它们可以简单地通过唯一的服务名称引用其他应用程序。Aspire 主机将进行地址解析,并根据服务名称提供正确的网络地址。

    因此,在这种情况下,API 应用程序的地址是否更改并不重要。Blazor UI 应用程序仍能够连接到正确的终结点。

    自动健康检查

    正如我们在仪表板上看到的,两个服务的状态都显示为**「正在运行**」。此状态可用,因为 .NET Aspire 具有一种自动评估服务运行状况的方法。此功能使我们能够查看我们的服务是否有任何问题,并立即采取适当的措施。

    此外,自动运行状况检查允许业务流程协调程序尝试修复失败的应用程序,而无需任何外部参与。这是通常首先发生的事情。

    同时监控所有组件

    但也许 Aspire 的最大好处是能够以方便和用户友好的方式在一个地方监控我们的整个分布式应用程序。

    我们可以查看每个服务的实时状态。我们可以查看所有日志。我们可以查看跟踪和指标。所有这一切都是通过通过直观的菜单导航并通过人类可读的名称选择正确的服务来完成的。

    在开发计算机上运行分布式应用

    分布式应用程序传统上因难以在本地开发机器上运行而臭名昭著。开发人员要么不得不花费数小时来配置他们的环境,要么只测试分布式应用程序的某些组件,同时模拟或模拟所有其他组件。

    .NET Aspire 使生成和调试整个分布式应用程序变得容易,即使它们非常复杂。我们需要做的就是向 Aspire 主机注册所有服务,其余的由编排器完成。

    接下来,让我们深入了解 .NET Aspir 的内部情况,了解它是如何进行业务流程的。

    设置 .NET Aspire Orchestrator

    .NET Aspire 是一个复杂的系统,有很多东西需要解压缩。我们将从基础开始。首先,我们将介绍使 .NET Aspire 工作的基本依赖关系。然后,我们将继续通过业务流程协调程序注册应用程序。

    安装 .NET Aspire 依赖项

    Aspire 应用程序需要一个主机应用程序,该应用程序充当业务流程协调程序。在上面的 Playground 中,项目扮演了这个角色。AspireApp.AppHost

    Aspire 主机项目需要 NuGet 包。如果我们打开文件,我们会发现这个包在第 17 行 被引用。Aspire.Hosting.AppHostAspireApp.AppHost.csproj

    主机应用程序还需要引用它要编排的所有应用程序。在我们的例子中,有两个:

  • AspireApp.Web,这是一个 Blazor Web 应用程序,其用户界面可从外部 REST API 终结点拉取数据。

  • AspireApp.ApiService,这是 Blazor 应用程序从中提取数据的 REST API 应用程序。

  • 一旦我们有了所有依赖项,我们就可以在主机应用程序的启动代码中促进业务流程。

    主机应用程序启动

    与任何 .NET 应用程序一样,可以在类中找到应用程序的启动代码。我们首先执行以下代码来创建主机生成器:AspireApp.AppHostProgram.cs

    var builder = DistributedApplication.CreateBuilder(args);

    与标准 ASP.NET Core 应用一样,我们首先实例化主机构建器。但是,这是 .NET Aspire 使用的特殊主机生成器。它来自专门用于构建主机应用程序的类。DistributedApplication

    然后,我们可以使用以下代码来执行此操作:

    var apiService = builder.AddProject<Projects.AspireApp_ApiService>("apiservice");

    这就是我们注册 API 应用程序的方式。对象 方法,它带有尖括号中的泛型类型规范,即 .这是一个自动生成的类型,其名称是这样推断的:builderAddProject()<Projects.AspireApp_ApiService>

  • 该部件指示它是解决方案中的项目之一。Project

  • 从项目名称推断出部件。这些点将替换为下划线,以确保它由单一类型表示。AspireApp_ApiServiceAspireApp.ApiService

  • 因此,这种类型在我们的任何项目中都不存在。这种类型是自动生成的,并从我们的一个项目的名称中推断出来。此类型将包含将所述项目注册为主机可以编排的应用程序所需的所有代码。

    该参数是我们注册服务时所使用的名称。在本例中,我们给它起的名字是 .apiservice

    注册 UI 应用程序 #

    注册 API 后,我们就可以注册 UI。在我们的示例项目中,这是如何完成的:

    builder.AddProject<Projects.AspireApp_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService);

    最初,我们执行的操作与注册 API 服务时相同。我们使用从项目推断出的类型引用来调用该方法。我们给这个项目起了个名字。AddProjectAspireApp.Webwebfrontend

    但是,我们在这里还做了另一件事。我们正在调用该方法,并将先前注册的 API 服务引用插入其中。这样做是为了让 Blazor UI 应用程序可以通过服务发现引用 API 应用程序。WithReference()

    如果我们再次查看仪表板,我们将看到其上显示的服务的名称为 webfrontend apiservice 。这些名称是任意的,但它们是我们在 Aspire 业务流程协调程序的启动代码中注册服务时使用的名称。

    服务器默认值简介

    .NET 上下文中的服务默认值 Aspire 是允许将应用添加到 Aspire 业务流程协调程序的扩展方法的集合。这些扩展方法作为 .NET Aspire 项目模板中包含的类库提供。

    这些扩展方法需要用于以下目的:

  • 启用服务发现

  • 使业务流程协调程序能够评估应用程序的运行状况

  • 从应用程序收集指标

  • 编排应用共享的其他常见功能

  • 这些扩展方法作为解决方案中的类库出现的原因是,可以修改它们以适应特定的业务方案。例如,开发人员可能希望选择与默认指标库不同的指标库。或者他们可能想要添加新功能。

    在我们的设置中,服务器默认值由项目表示。由于此项目是一个类库,其目的是为 ASP.NET Core 应用程序的启动代码添加扩展方法,因此此项目需要引用框架包。AspireApp.ServiceDefaultsMicrosoft.AspNetCore.App

    此包允许库访问 ASP.NET Core 使用的类型,例如 和 。IHostApplicationBuilderWebApplication

    我们还需要添加对其他包的引用,这些包支持我们正在寻找的特定功能,例如服务发现和遥测。

    添加扩展方法

    扩展方法可以在项目的文件中找到。这是它的内容:

    usingMicrosoft.AspNetCore.Builder;
    usingMicrosoft.AspNetCore.Diagnostics.HealthChecks;
    usingMicrosoft.Extensions.DependencyInjection;
    usingMicrosoft.Extensions.Diagnostics.HealthChecks;
    usingMicrosoft.Extensions.Logging;
    usingOpenTelemetry;
    usingOpenTelemetry.Metrics;
    usingOpenTelemetry.Trace;
    namespaceMicrosoft.Extensions.Hosting;
    // Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
    // This project should be referenced by each service project in your solution.
    // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
    publicstatic classExtensions
    {
    publicstaticIHostApplicationBuilderAddServiceDefaults(thisIHostApplicationBuilder builder)
    {
    builder.ConfigureOpenTelemetry();
    builder.AddDefaultHealthChecks();
    builder.Services.AddServiceDiscovery();
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
    // Turn on resilience by default
    http.AddStandardResilienceHandler();
    // Turn on service discovery by default
    http.AddServiceDiscovery();
    });
    return builder;
    }
    publicstaticIHostApplicationBuilderConfigureOpenTelemetry(thisIHostApplicationBuilder builder)
    {
    builder.Logging.AddOpenTelemetry(logging =>
    {
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
    });
    builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
    metrics.AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation()
    .AddRuntimeInstrumentation();
    })
    .WithTracing(tracing =>
    {
    tracing.AddAspNetCoreInstrumentation()
    // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
    //.AddGrpcClientInstrumentation()
    .AddHttpClientInstrumentation();
    });
    builder.AddOpenTelemetryExporters();
    return builder;
    }
    privatestaticIHostApplicationBuilderAddOpenTelemetryExporters(thisIHostApplicationBuilder builder)
    {
    var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
    if (useOtlpExporter)
    {
    builder.Services.AddOpenTelemetry().UseOtlpExporter();
    }
    // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
    //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
    //{
    // builder.Services.AddOpenTelemetry()
    // .UseAzureMonitor();
    //}
    return builder;
    }
    publicstaticIHostApplicationBuilderAddDefaultHealthChecks(thisIHostApplicationBuilder builder)
    {
    builder.Services.AddHealthChecks()
    // Add a default liveness check to ensure app is responsive
    .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
    return builder;
    }
    publicstaticWebApplicationMapDefaultEndpoints(thisWebApplication app)
    {
    // Adding health checks endpoints to applications in non-development environments has security implications.
    // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
    if (app.Environment.IsDevelopment())
    {
    // All health checks must pass for app to be considered ready to accept traffic after starting
    app.MapHealthChecks("/health");
    // Only health checks tagged with the "live" tag must pass for app to be considered alive
    app.MapHealthChecks("/alive", newHealthCheckOptions
    {
    Predicate = r => r.Tags.Contains("live")
    });
    }
    return app;
    }
    }

    在此文件中,我们结合了这两种扩展方法和类型,这两种方法都用于 ASP.NET Core 应用程序的启动代码中。IHostApplicationBuilderWebApplication

    用于注册应用程序可以使用的服务。该类型用于向应用程序添加 HTTP 终结点,以及向请求处理管道添加步骤。IHostApplicationBuilderWebApplication

    我们将在本章的相关课程中详细介绍这些方法中的每一种。但是,以下是每种方法的用途的快速摘要:

  • 第 34 行 的方法用于注册可以在应用程序内收集指标的服务。ConfigureOpenTelemetry()

  • 第 68 行 的方法配置了将指标导出应用程序的功能,以便它们可用于监视应用程序。AddOpenTelemetryExporters()

  • 第 14 行 的方法调用这两个方法,并执行一些额外的依赖项注册,例如添加服务发现所需的服务。AddServiceDefaults()

  • 第 102 行 的方法将运行状况检查终结点添加到应用程序。MapDefaultEndpoints()

  • 一旦我们有了这些方法,我们将需要从相关应用程序的启动代码中调用它们。

    使用扩展方法

    如果我们在项目中打开文件,我们将能够找到以下行:Program.csAspireApp.Web

    builder.AddServiceDefaults();

    这调用了我们之前查看的扩展方法。这就是我们注册与 .NET Aspire 关联的所有依赖项的方式。这允许此应用程序可由 Aspire 业务流程协调程序管理。

    我们还在文件下方进行了以下调用:

    app.MapDefaultEndpoints();

    这又是我们在服务默认值中添加的扩展方法之一。

    如果我们在项目中打开文件,我们会发现正在调用相同的扩展方法。Program.csAspireApp.ApiService

    .NET Aspire 中的服务发现

    服务发现是分布式系统和微服务架构的一个关键方面。它是指在网络环境中自动检测和注册可用服务的过程。在微服务体系结构中,应用程序由许多通过网络相互通信的小型独立服务组成,服务发现使这些服务能够动态定位并相互通信,而无需手动配置。

    服务发现涉及几个关键组件:

  • 服务注册 :当微服务启动时,它会向服务注册中心或发现服务注册。此注册通常包括服务的网络位置(例如 IP 地址和端口)、元数据以及可能的运行状况等信息。

  • 服务查询 :需要与特定服务交互的其他微服务或客户端可以查询服务注册中心,以发现其位置和其他相关详细信息。此查询可以在运行时动态执行,使服务能够适应网络拓扑的变化或服务的添加/删除。

  • 动态更新 :服务发现系统支持对注册表的动态更新,允许服务加入或离开网络而不会造成中断。这可确保注册表始终反映系统的当前状态。

  • 负载均衡 :服务发现通常与负载均衡机制集成,以在服务的多个实例之间分配传入请求。这通过均匀分配工作负载来提高系统的可伸缩性、容错性和性能。

  • 注册服务发现组件

    在使用 .NET Aspire 启用服务发现之前,我们需要在要添加到业务流程协调程序的应用程序中安装 NuGet 包。我们在项目中就是这样做的。Microsoft.Extensions.ServiceDiscoveryAspireApp.ServiceDefaults

    启用服务发现

    接下来,我们需要启用服务发现,以便应用程序可以使用它。在我们的示例中,这是在类库文件的方法中完成的。AddServiceDefaults()Extensions.csAspireApp.ServiceDefaults

    下面是此方法中的特定调用,用于注册服务发现所需的所有依赖项:

  • builder.Services.AddServiceDiscovery();

  • http.AddServiceDiscovery();

  • 一旦我们注册了所有必需的组件,就可以开始使用服务发现了。

    使用服务发现

    如果我们打开项目文件夹下的文件,我们将找到一个服务发现用法的示例。Program.csAspireApp.Web

    首先,我们需要确保注册所有相关组件,这是通过调用扩展方法完成的。然后,在我们创建用于连接到 API 应用程序的 HTTP 客户端的块中,我们有以下内容:AddServiceDefaults()

    client.BaseAddress = new("https+http://apiservice");

    通常,我们会使用标准 URL 作为基址,但这次不是。相反,我们将 HTTP 客户端指向我们想要获取其地址的服务的名称。该名称是我们在 .NET Aspire 业务流程协调程序中注册此服务的名称。

    在此示例中,我们将协议定义为 。这意味着我们将尝试通过HTTPS连接到服务,但如果我们不能,我们将使用HTTP。但是,首选 HTTPS。https+http

    然后我们有一个标准冒号,后跟一个双斜杠,我们在任何 URL 中使用它。最后,我们有了要连接的服务的名称,即 apiservice

    在此设置中,注册为 apiservice 的应用程序可以移动到其他服务器。其地址可能会更改。它的端口可能会改变。但对于我们的客户应用程序来说,这些都无关紧要,因为它不知道它的地址,也不关心它。Aspire 主机应用程序将解析要连接到的正确地址。

    结束语

    正如我们所看到的,Aspire 受到 .NET 社区的喜爱绝非偶然。与其他编排器相比,它使在本地构建分布式应用程序的过程变得毫不费力。

    如果你喜欢我的文章,请给我一个赞!谢谢