你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

任务关键型工作负载的应用程序设计注意事项

基线任务关键型参考体系结构使用一个简单的在线目录应用程序说明了高度可靠的工作负载。 用户可以浏览项目录,查看项详细信息,并发布项评级和项注释。 本文重点介绍任务关键型应用程序的可靠性和复原能力,例如请求的异步处理以及如何在解决方案中实现高吞吐量。

重要

GitHub logo 生产级参考实现展示了 Azure 上的任务关键型应用程序开发,它支持本文中的指导。 在生产的第一步中,你可以使用此实现作为进一步进行解决方案开发的基础。

应用程序的构成

对于大规模任务关键型应用程序,必须优化体系结构以实现端到端的可伸缩性和复原能力。 可以将组件分离为可以独立操作的功能单元。 在应用程序堆栈的所有级别上进行此分离,以便系统的每个部分独立缩放并符合需求的变化。 该实现演示了此方法。

应用程序使用无状态 API 终结点通过消息代理异步解耦长时间运行的写入请求。 工作负载的组合使你能够随时删除和重新创建整个 Azure Kubernetes 服务 (AKS) 群集和标记中的其他依赖项。 应用程序的主要组件有:

  • 用户界面 (UI):用户可以访问的单页 Web 应用程序。 UI 托管在 Azure 存储帐户的静态网站托管中。

  • API (CatalogService):UI 应用程序调用的 REST API,但仍可用于其他潜在的客户端应用程序。

  • 工作器 (BackgroundProcessor):后台工作器,可侦听消息总线上的新事件并处理对数据库的写入请求。 该组件不公开任何 API。

  • 运行状况服务 API (HealthService):一种 API,通过检查关键组件(例如,数据库或消息总线)是否正常运行来报告应用程序的运行状况。

    显示应用程序流的示意图。

工作负载由 API、工作器和运行状况检查应用程序组成。 名为 workload 的专用 AKS 命名空间将工作负载作为容器托管。 Pod 之间不进行直接通信。 Pod 是无状态的,并且能够独立缩放。

显示工作负载的详细组成的图表。

在群集中运行的其他支持组件包括:

  • NGINX 入口控制器:将传入请求路由到工作负载,并在 Pod 之间实现负载均衡。 NGINX 入口控制器通过 Azure 负载均衡器公开,并使用公共 IP 地址(但只能通过 Azure Front Door 访问)。

  • 证书管理器:Jetstack 的通过使用 Let's Encrypt 作为入口规则的 cert-manager 自动预配传输层安全性 (TLS) 证书。

  • 机密存储 CSI 驱动程序:适用于机密存储 CSI 驱动程序的 Azure Key Vault 提供程序从 Key Vault 安全地读取连接字符串等机密。

  • 监视代理:调整默认 OMSAgentForLinux 配置,减少发送到 Azure Monitor 日志工作区的监视数据量。

数据库连接

由于部署标记是临时的,因此请尽可能避免在该标记中持久保存状态。 应将状态持久保存在外部数据存储中。 若要支持可靠性服务级别目标 (SLO),请创建可复原的数据存储。 建议将托管或平台即服务 (PaaS) 解决方案与可自动处理超时、连接断开和其他故障状态的本机 SDK 库结合使用。

在参考实现中,Azure Cosmos DB 用作应用程序的主要数据存储。 Azure Cosmos DB 提供多区域写入。 每个标记都可以写入在内部处理区域间数据复制和同步的 Azure Cosmos DB 所在区域中的 Azure Cosmos DB 副本。 Azure Cosmos DB for NoSQL 支持数据库引擎的所有功能。

有关详细信息,请参阅任务关键型工作负载的数据平台

注意

对于新应用程序,请使用 Azure Cosmos DB for NoSQL。 对于使用其他 NoSQL 协议的旧版应用程序,请评估通往 Azure Cosmos DB 的迁移路径。

对于优先考虑可用性而不是性能的任务关键型应用程序,建议使用具有强一致性级别的单区域写入和多区域读取。

此体系结构使用存储将状态临时存储在 Azure 事件中心检查点的标记中。

所有工作负载组件都使用 Azure Cosmos DB .NET Core SDK 与数据库进行通信。 SDK 包含用于维护数据库连接和处理故障的健壮逻辑。 关键配置设置包括:

  • 直接连接模式:此设置是 .NET SDK v3 的默认设置,因为它提供更好的性能。 与使用 HTTP 的网关模式相比,直接连接模式的网络跃点更少。

  • 写入时返回内容响应:此方式已禁用,因此 Azure Cosmos DB 客户端无法从创建、更新插入、打补丁和替换操作返回文档,从而减少网络流量。 客户端上的进一步处理不需要此设置。

  • 自定义序列化:此过程将 JSON 属性命名策略设置为 JsonNamingPolicy.CamelCase,以将 .NET 属性转换为标准 JSON 属性。 它还可以将 JSON 属性转换为 .NET 属性。 默认忽略条件在序列化期间忽略包含 null 值的属性(例如,JsonIgnoreCondition.WhenWritingNull)。

  • ApplicationRegion:此属性设置为标记的区域,使 SDK 能够找到最近的连接终结点。 终结点最好位于同一区域。

以下代码块出现在引用实现中:

//
// /src/app/AlwaysOn.Shared/Services/CosmosDbService.cs
//
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(sysConfig.CosmosEndpointUri, sysConfig.CosmosApiKey)
    .WithConnectionModeDirect()
    .WithContentResponseOnWrite(false)
    .WithRequestTimeout(TimeSpan.FromSeconds(sysConfig.ComsosRequestTimeoutSeconds))
    .WithThrottlingRetryOptions(TimeSpan.FromSeconds(sysConfig.ComsosRetryWaitSeconds), sysConfig.ComsosMaxRetryCount)
    .WithCustomSerializer(new CosmosNetSerializer(Globals.JsonSerializerOptions));

if (sysConfig.AzureRegion != "unknown")
{
    clientBuilder = clientBuilder.WithApplicationRegion(sysConfig.AzureRegion);
}

_dbClient = clientBuilder.Build();

异步消息传递

实现松散耦合时,服务不依赖于其他服务。 松散性使得服务能够独立运行。 耦合性允许通过明确定义的接口进行服务间通信。 对于任务关键型应用程序,松散耦合可防止下游故障级联到前端或其他部署标记,从而提供高可用性。

异步消息传递的主要特征包括:

  • 服务不限于使用同一计算平台、编程语言或操作系统。

  • 服务独立缩放。

  • 下游故障不影响客户端事务。

  • 事务完整性更难维护,因为数据创建和保存在不同的服务中进行。 事务完整性是跨消息传递和持久性服务的一个挑战。 有关详细信息,请参阅幂等消息处理

  • 端到端跟踪所需的业务流程很复杂。

建议使用常用设计模式,例如基于队列的负载均衡模式竞争使用者模式。 这些模式将负载从生产者分配给使用者,且有助于使用者进行异步处理。 例如,工作器允许 API 接受请求并快速返回给调用方,工作器单独处理数据库写入操作。

事件中心在 API 和工作器之间代理消息。

重要

请勿将消息代理长期用作持久性数据存储。 事件中心服务支持捕获功能。 事件中心可利用捕获功能自动将消息的副本写入链接的存储帐户。 此进程控制使用情况,并作为备份消息的机制。

写入操作的实现详细信息

帖子评分、帖子评论等写入操作都以异步方式处理。 API 首先将包含所有相关信息(例如操作类型和评论数据)的消息发送到消息队列,并立即返回 HTTP 202 (Accepted) 以及待创建对象的 Location 标头。

BackgroundProcessor 实例处理队列中的消息,并处理写入操作的实际数据库通信。 BackgroundProcessor 根据队列消息量动态地横向缩减和横向扩展。 处理器实例的横向扩展限制由事件中心分区的最大数量(基本层和标准层为 32,高级层为 100,专用层为 1,024)定义。

该图显示了实现中帖子评分功能的异步性。

BackgroundProcessor 中的 Azure 事件中心处理器库使用 Azure Blob 存储来管理分区所有权、在各工作器实例之间进行负载均衡,并使用检查点跟踪进度。 检查点不会在每个事件后写入 Blob 存储,因为这会为每条消息增加昂贵的延迟。 相反,检查点是在计时器循环上编写的,你可以配置持续时间。 默认设置为 10 秒。

以下代码块出现在引用实现中:

while (!stoppingToken.IsCancellationRequested)
{
    await Task.Delay(TimeSpan.FromSeconds(_sysConfig.BackendCheckpointLoopSeconds), stoppingToken);
    if (!stoppingToken.IsCancellationRequested && !checkpointEvents.IsEmpty)
    {
        string lastPartition = null;
        try
        {
            foreach (var partition in checkpointEvents.Keys)
            {
                lastPartition = partition;
                if (checkpointEvents.TryRemove(partition, out ProcessEventArgs lastProcessEventArgs))
                {
                    if (lastProcessEventArgs.HasEvent)
                    {
                        _logger.LogDebug("Scheduled checkpointing for partition {partition}. Offset={offset}", partition, lastProcessEventArgs.Data.Offset);
                        await lastProcessEventArgs.UpdateCheckpointAsync();
                    }
                }
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Exception during checkpointing loop for partition={lastPartition}", lastPartition);
        }
    }
}

如果处理器应用程序发生错误或在处理消息之前停止,则:

  • 另一个实例将接收消息进行重新处理,因为该消息没有在存储中正确设置检查点。

  • 如果上一个工作器在工作器失败之前将文档持久保存在数据库中,则会发生冲突。 发生此错误的原因是使用了相同的 ID 和分区键。 处理器可以安全地忽略该消息,因为该文档已被持久化。

  • 如果上一个工作器在写入数据库之前被终止,则新实例会重复这些步骤并最终实现持久性。

读取操作的实现详细信息

API 直接处理读取操作,并立即将数据返回给用户。

显示读取操作过程的图表。

如果操作成功完成,则不会建立用于与客户端通信的反向通道方法。 客户端应用程序必须主动轮询 API 以更新 Location HTTP 标头中指定的项。

伸缩性

各个工作负载组件应独立横向扩展,因为每个组件都有不同的负载模式。 缩放要求取决于服务的功能。 某些服务直接影响用户,必须积极横向扩展,以确保快速响应和积极的用户体验。

实现将服务打包为容器映像,并使用 Helm 图表将服务部署到每个标记。 这些服务被配置为具有预期的 Kubernetes 请求和限制,并使用预配置的自动缩放规则。 CatalogServiceBackgroundProcessor 工作负载组件可以独立横向缩减和横向扩展,这两种服务都是无状态的。

用户直接与 CatalogService 交互,因此工作负载的这一部分必须在任意负载下做出响应。 每个群集至少有 3 个实例分布在 Azure 区域的三个可用性区域中。 AKS 中的水平 Pod 自动缩放程序 (HPA) 会根据需要自动添加更多 Pod。 Azure Cosmos DB 自动缩放功能可以动态增加和减少可用于集合的请求单位 (RU)。 CatalogService 和 Azure Cosmos DB 共同构成标记中的缩放单元。

HPA 通过 Helm 图表进行部署,该图表具有可配置的最大和最小副本数。 负载测试确定,使用标准使用模式,每个实例每秒可以处理大约 250 个请求。

BackgroundProcessor 服务的要求不同,是对用户体验影响有限的后台工作器。 因此,与 CatalogService 相比,BackgroundProcessor 具有不同的自动缩放配置,并且可以在 2 到 32 个实例之间进行缩放。 根据在事件中心中使用的分区数确定此限制。 不需要比分区更多的工作器。

组件 minReplicas maxReplicas
CatalogService 3 20
BackgroundProcessor 2 32

工作负载的每个组件(包括 ingress-nginx 等依赖项)都配置了 Pod Disruption Budgets (PDB) 设置,以确保在群集更改时保持有最少数量的实例可用。

以下代码块出现在引用实现中:

#
# /src/app/charts/healthservice/templates/pdb.yaml
# Example pod distribution budget configuration.
#
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ .Chart.Name }}-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}

注意

通过负载测试确定每个组件的实际最小 Pod 数量和最大 Pod 数量。 每个工作负荷的 Pod 数可能有所不同。

检测

使用检测来评估工作负载组件可能引入系统的性能瓶颈和运行状况问题。 为帮助量化决策,每个组件都应通过指标和跟踪日志发出足够的信息。 检测应用程序时,请考虑以下关键注意事项:

  • 将日志、指标和其他遥测数据发送到标记的日志系统。
  • 使用结构化日志记录而不是纯文本,以便查询信息。
  • 实现事件关联以获取端到端的事务视图。 在引用实现中,每个 API 响应都包含操作 ID 作为 HTTP 标头,用于追溯。
  • 不要只依赖 stdout 日志记录或控制台日志记录。 但是,可以使用这些日志立即对出现故障的 Pod 进行故障排除。

此体系结构使用 Application Insights 和 Azure Monitor 日志工作区实现分布式跟踪,以便应用程序监视数据。 将 Azure Monitor 日志用于所有工作负载和基础结构组件的日志和指标。 此体系结构实现对来自 API、通过事件中心、然后到 Azure Cosmos DB 的请求的完全端到端跟踪。

重要

将标记监视资源部署到单独的监视资源组。 资源的生命周期与标记本身不同。 有关详细信息,请参阅监视标记资源数据

独立全局服务、监视服务和标记部署的图。

应用程序监视的实现详细信息

BackgroundProcessor 组件使用 Microsoft.ApplicationInsights.WorkerService NuGet 包从应用程序中获取立即可用的检测。 Serilog 还用于应用程序内的所有日志记录。 Application Insights 被配置为除控制台接收器之外的接收器。 只有需要跟踪其他指标时,才会直接使用 Application Insights 的 TelemetryClient 实例。

以下代码块出现在引用实现中:

//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        Log.Logger = new LoggerConfiguration()
                            .ReadFrom.Configuration(hostContext.Configuration)
                            .Enrich.FromLogContext()
                            .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                            .WriteTo.ApplicationInsights(hostContext.Configuration[SysConfiguration.ApplicationInsightsConnStringKeyName], TelemetryConverter.Traces)
                            .CreateLogger();
    }

端到端跟踪功能的屏幕截图。

为演示实际的请求可追溯性,每个 API 请求(无论成功与否)都将关联 ID 标头返回给调用方。 应用程序支持团队可以使用此标识符搜索 Application Insights,并获取完整事务的详细视图,如上图所示。

以下代码块出现在引用实现中:

//
// /src/app/AlwaysOn.CatalogService/Startup.cs
//
app.Use(async (context, next) =>
{
    context.Response.OnStarting(o =>
    {
        if (o is HttpContext ctx)
        {
            // ... code omitted for brevity
            context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
            context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
            context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
        }
        return Task.CompletedTask;
    }, context);
    await next();
});

注意

默认情况下,自适应采样在 Application Insights SDK 中启用。 自适应采样意味着并非每个请求都发送到云且可按 ID 进行搜索。 任务关键型应用程序团队需要能够可靠地跟踪每个请求,因此参考实现在生产环境中禁用了自适应采样。

Kubernetes 监视实现详细信息

可以使用诊断设置将 AKS 日志和指标发送到 Azure Monitor 日志。 还可以将容器见解功能与 AKS 配合使用。 启用容器见解会通过 Kubernetes DaemonSet 将 OMSAgentForLinux 部署到 AKS 群集中的每个节点上。 OMSAgentForLinux 可以从 Kubernetes 群集中收集更多日志和指标,并发送到其相应的 Azure Monitor 日志工作区。 此工作区包含有关 Pod、部署、服务和群集整体运行状况的更细粒度的数据。

广泛的日志记录会提高成本,且没有任何好处。 因此,在容器见解配置中禁用了工作负载 pod 的 stdout 日志收集和 Prometheus 抓取,因为所有跟踪都已通过 Application Insights 捕获,其中生成重复记录。

以下代码块出现在引用实现中:

#
# /src/config/monitoring/container-azm-ms-agentconfig.yaml
# This is just a snippet showing the relevant part.
#
[log_collection_settings]
    [log_collection_settings.stdout]
        enabled = false

        exclude_namespaces = ["kube-system"]

有关详细信息,请参阅完整配置文件

应用程序运行状况监视

可以使用应用程序监视和可观测性来快速识别系统问题,并向运行状况模型通知当前应用程序状态。 可以通过运行状况终结点来呈现运行状况监视。 运行状况探测使用运行状况监视数据来提供信息。 主负载均衡器使用该信息立即停止运行不正常的组件。

此体系结构在以下级别应用运行状况监视:

  • 在 AKS 上运行的工作负载 Pod。 这些 pod 具有运行状况探测和运行情况探测,因此 AKS 能够管理其生命周期。

  • 运行状况服务是群集中的一个专用组件。 根据配置,Azure Front Door 探测每个标记中的运行状况服务,并自动删除负载均衡中运行不正常的标记。

运行状况服务实现详细信息

HealthService 是与计算群集上的其他组件(如 CatalogServiceBackgroundProcessor)共同运行的工作负载组件。 HealthService 提供了一个 REST API,Azure Front Door 运行状况检查调用该 API 来确定标记的可用性。 与基本的运行情况探测不同,运行状况服务组件更加复杂,除了自己的状态以外,它还提供依赖项的状态。

查询 Azure Cosmos DB、事件中心和存储的运行状况服务的图。

如果 AKS 群集已关闭,运行状况服务将不会响应,从而导致工作负载运行不正常。 服务运行时,它会定期检查解决方案的关键组件。 所有检查都是以异步并行方式完成的。 如果任何检查失败,则整个标记不可用。

警告

Azure Front Door 运行状况探测会在运行状况服务上实施大量负载,因为请求来自多个接入点 (PoP) 位置。 为了防止下游组件过载,请实现有效的缓存。

运行状况服务还用于使用每个标记的 Application Insights 资源显式配置的 URL ping 测试。

有关 HealthService 实现的详细信息,请参阅应用程序运行状况服务

下一步