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

适用于 Java 的新式 Web 应用模式

Azure 应用服务
Azure 服务总线

本文介绍如何实现新式 Web 应用模式。 新式 Web 应用模式定义如何实现云 Web 应用的现代化并引入面向服务的体系结构。 该模式提供规范性的体系结构、代码和配置指南,这些指南符合 azure Well-Architected Framework原则。 此模式基于 Reliable Web App 模式构建。

为什么要使用新式 Web 应用模式?

新式 Web 应用模式可帮助你优化 Web 应用程序的高需求区域。 它提供了相关详细指南,用于分离这些区域,以实现独立缩放以实现成本优化。 此方法使你可以将专用资源分配给关键组件,从而提高整体性能。 分离可分离的服务可以通过防止应用一部分的减速影响他人来提高可靠性。 它还支持对各个应用组件进行独立版本控制。

如何实现新式 Web 应用模式

本文包含实现新式 Web 应用模式的指南。 使用以下链接转到所需的特定指南:

  • 体系结构指南。 了解如何模块化 Web 应用组件,并选择适当的平台即服务(PaaS)解决方案。
  • 代码指南。 实现四种设计模式以优化分离组件:Strangler Fig、Queue-Based 负载调配、竞争使用者和运行状况终结点监视。
  • 配置指南。 为分离的组件配置身份验证、授权、自动缩放和容器化。

提示

GitHub 徽标 现代 Web 应用模式有一个 参考实现(示例应用)。 它表示现代 Web 应用实现的结束状态。 它是一个生产级 Web 应用,它提供本文中讨论的所有代码、体系结构和配置更新。 部署并使用参考实现来指导你实现新式 Web 应用模式。

体系结构指南

新式 Web 应用模式建立在可靠 Web 应用模式的基础上。 它需要一些额外的体系结构组件。 需要消息队列、容器平台、存储服务和容器注册表,如下图所示:

显示新式 Web 应用模式基线体系结构的关系图。

为了实现更高的服务级别目标 (SLO),可以在 Web 应用体系结构中添加第二个区域。 根据业务需求,将负载均衡器配置为将流量路由到第二个区域,以支持主动-主动或主动-被动配置。 这两个区域需要相同的服务,但一个区域有一个中心虚拟网络。 使用中心辐射型网络拓扑来集中和共享资源,例如网络防火墙。 通过中心虚拟网络访问容器存储库。 如果有虚拟机,请将堡垒主机添加到中心虚拟网络,以增强安全性来管理它们。 下图显示了此体系结构:

显示具有第二个区域的现代 Web 应用模式体系结构的关系图。

分离体系结构

要实现新式 Web 应用模式,需要将现有的 Web 应用体系结构分离。 将体系结构分离需要将整体式应用程序分解为较小的独立服务,每个服务负责特定的功能或功能。 此过程包括评估当前 Web 应用、修改体系结构,最后将 Web 应用代码提取到容器平台。 目标是系统地识别和提取从分离中获益最大的应用程序服务。 要实现体系结构分离,请遵循以下建议:

  • 标识服务边界。 应用域驱动的设计原则,以识别整体应用程序中的有限上下文。 每个边界上下文表示逻辑边界,并且是分离的候选项。 表示不同业务功能且依赖项较少的服务是很好的候选项。

  • 评估服务优势。 重点关注从独立缩放中获益最大的服务。 例如,外部依赖项(如 LOB 应用程序中的电子邮件服务提供商)可能需要与故障隔离更多。 请考虑经常更新或更改的服务。 分离这些服务可实现独立的部署,并降低影响应用程序其他部分的风险。

  • 评估技术可行性。 检查当前体系结构,确定可能影响分离过程的技术限制和依赖项。 规划如何跨服务管理和共享数据。 已分离服务应管理自己的数据,并要尽量减少跨服务边界的直接数据库访问。

  • 部署 Azure 服务。 选择并部署需要支持要提取的 Web 应用服务的 Azure 服务。 有关指南,请参阅 选择本文 部分的正确 Azure 服务。

  • 分离 Web 应用服务。 定义新提取的 Web 应用服务可用于与系统的其他部分交互的明确接口和 API。 设计数据管理策略,允许每个服务管理自己的数据,但可确保一致性和完整性。 有关在此提取过程中要使用的特定实现策略和设计模式,请参阅 代码指南 部分。

  • 对已分离服务使用独立存储。 若要简化版本控制和部署,请确保每个分离服务都有自己的数据存储。 例如,引用实现将电子邮件服务与 Web 应用分开,并且无需服务访问数据库。 相反,该服务会通过Azure 服务总线消息将电子邮件传递状态传回 Web 应用,Web 应用会将笔记保存到其数据库。

  • 为每个已分离服务实现单独的部署管道。 如果实现单独的部署管道,则可以根据自己的计划更新每个服务。 如果公司内的不同团队或组织拥有不同的服务,则使用单独的部署管道可让每个团队控制自己的部署。 使用 Jenkins、GitHub Actions 或 Azure Pipelines 等持续集成和持续交付(CI/CD)工具设置这些管道。

  • 修订安全控制。 确保更新安全控制以适应新的体系结构,包括防火墙规则和访问控制。

选择正确的 Azure 服务

对于体系结构中的每个 Azure 服务,请查阅“架构良好的框架”中的相关 Azure 服务指南。 对于新式 Web 应用模式,需要一个支持异步消息传递的消息传递系统、一个支持容器化的应用程序平台和一个容器映像存储库。

  • 选择消息队列。 消息队列是面向服务的体系结构的重要组成部分。 它使消息发送方与接收方分离,从而实现异步消息传送。 使用有关选择 Azure 消息传送服务的的指导来选择支持你的设计需求的 Azure 消息传送系统。 Azure 有三个消息传送服务:Azure 事件网格、Azure 事件中心和服务总线。 从服务总线开始,如果服务总线不满足需求,请使用另外两个选项之一。

    服务 用例
    服务总线 为企业应用程序中高价值消息的可靠、有序和可能事务传递选择服务总线。
    事件网格 如果需要高效地处理大量离散事件,请选择事件网格。 事件网格对于事件驱动的应用程序可缩放,其中许多小型独立事件(如资源状态更改)需要在低延迟发布-订阅模型中路由到订阅服务器。
    事件中心 选择事件中心进行大规模高吞吐量的数据引入,例如遥测、日志或实时分析。 事件中心针对需要持续引入和处理批量数据的流式处理方案进行了优化。
  • 实现容器服务。 对于要容器化的应用程序元素,需要一个支持容器的应用程序平台。 选择 Azure 容器服务 指南可帮助你选择一个。 Azure 有三个主要容器服务:Azure 容器应用、Azure Kubernetes 服务(AKS)和Azure App 服务。 从容器应用开始,如果容器应用不满足需求,请使用另外两个选项之一。

    服务 用例
    容器应用 如果需要自动缩放和管理事件驱动应用程序中的容器的无服务器平台,请选择“容器应用”。
    AKS 如果需要对 Kubernetes 配置进行详细控制,并需要缩放、网络和安全方面的高级功能,请选择 AKS。
    用于容器的 Web 应用 在应用服务中选择用于容器的 Web 应用,以获取最简单的 PaaS 体验。
  • 实现容器存储库。 使用基于容器的计算服务时,需要有一个存储库来存储容器映像。 可以使用 Docker 中心等公共容器注册表或 Azure 容器注册表等托管注册表。 Azure 中容器注册表的 简介 指南可帮助你选择一个注册表。

代码指导

若要成功分离和提取独立服务,需要使用以下设计模式更新 Web 应用代码:Strangler Fig、Queue-Based 负载调配、竞争使用者、运行状况终结点监视和重试。 下图显示了这些模式的角色:

图显示了现代 Web 应用模式体系结构中设计模式的角色。

  1. Strangler Fig 模式:Strangler Fig 模式会将功能从整体应用程序逐步迁移到已分离服务。 在主 Web 应用中实现此模式,通过根据终结点引导流量,将功能逐步迁移到独立服务中。

  2. 基于队列的负载调配模式:基于队列的负载调配模式通过使用队列作为缓冲区来管理生成者和使用者之间的消息流。 在分离服务的生成者部分中实现此模式,以使用队列异步管理消息流。

  3. 竞争使用者模式:竞争使用者模式使分离服务的多个实例能够独立读取同一消息队列并争用处理消息。 在分离服务中实现此模式,将任务分配到多个实例中。

  4. 运行状况终结点监视模式:运行状况终结点监视模式公开用于监视 Web 应用不同组件的状态和运行状况的终结点。 (4a) 在主 Web 应用中实现此模式。 (4b) 同时在已分离服务中实现该功能,以跟踪终结点的运行状况。

  5. 重试模式:重试模式通过重试可能间歇性失败的操作来处理暂时性故障。 (5a) 主 Web 应用中在所有对其他 Azure 服务的出站调用(例如对消息队列和专用终结点的调用)上实现此模式。 (5b) 同时在已分离服务中实现此模式,以处理调用私有终结点时出现的暂时性故障。

每个设计模式都提供了与 Well-Architected 框架的一个或多个支柱保持一致的好处。 下表提供了详细信息。

设计模式 实现位置 可靠性 (RE) 安全性 (SE) 成本优化 (CO) 卓越运营 (OE) 性能效率 (PE) 支持架构良好的框架原则
Strangler Fig 模式 主 Web 应用 RE:08
CO:07
CO:08
OE:06
OE:11
基于队列的负载调节模式 已分离服务的生成者 RE:06
RE:07
CO:12
PE:05
使用者竞争模式 已分离服务 RE:05
RE:07
CO:05
CO:07
PE:05
PE:07
运行状况终结点监视模式 主 Web 应用和分离服务 RE:07
RE:10
OE:07
PE:05
重试模式 主 Web 应用和分离服务 RE:07

实现 Strangler Fig 模式

使用 Strangler Fig 模式逐步将功能从整体代码库迁移到新的独立服务。 从现有的整体代码库中提取新的服务,并逐步实现 Web 应用关键部分的现代化。 要实现 Strangler Fig 模式,请遵循以下建议:

  • 设置路由层。 在整体 Web 应用代码库中,实现基于终结点定向流量的路由层。 根据需要使用自定义路由逻辑,以处理引导流量的特定业务规则。 例如,如果在整体应用中具有 /users 终结点,并且将该功能移动到分离的服务,则路由层会将所有请求定向到 /users 到新服务。

  • 管理功能推出。实现功能标志分阶段推出以逐步推出分离服务。 现有的整体应用路由应控制分离服务接收的请求数。 从少量的请求开始,随着你对服务的稳定性和性能充满信心,随着时间的推移增加使用量。

    例如,参考实现将电子邮件传递功能提取到独立服务中。 可以逐步引入该服务来处理包含 Contoso 支持指南的电子邮件的较大百分比请求。 随着新服务证明其可靠性和性能,它最终可以从整体上接管整个电子邮件责任集,完成转换。

  • 使用外观服务(如有必要)。 当单个请求需要与多个服务交互,或者想要从客户端隐藏基础系统的复杂性时,外观服务非常有用。 但是,如果分离的服务没有任何面向公众的 API,则可能不需要外观服务。

    在整体 Web 应用代码库中,实现外观服务,将请求路由到适当的后端(整体或微服务)。 确保通过外墙访问新的分离服务可以独立处理请求。

实现基于队列的负载调节模式

分离服务的生成者部分中实现基于队列的负载调配模式 ,以异步处理不需要即时响应的任务。 这种模式通过使用队列来管理工作负荷分配,提高了系统的整体响应速度和可伸缩性。 它使分离服务能够以一致的速率处理请求。 要有效实现这种模式,请遵循以下建议:

  • 使用非阻止消息队列。 确保将消息发送到队列的进程不会在等待分离服务处理队列中的消息时阻止其他进程。 如果进程需要分离服务操作的结果,请实现一种替代方法,以便在等待排队操作完成时处理情况。 例如,在 Spring Boot 中,可以使用 StreamBridge 类将消息异步发布到队列,而无需阻止调用线程:

    private final StreamBridge streamBridge;
    
    public SupportGuideQueueSender(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }
    
    // Asynchronously publish a message without blocking the calling thread
    @Override
    public void send(String to, String guideUrl, Long requestId) {
        EmailRequest emailRequest = EmailRequest.newBuilder()
                .setRequestId(requestId)
                .setEmailAddress(to)
                .setUrlToManual(guideUrl)
                .build();
    
        log.info("EmailRequest: {}", emailRequest);
    
        var message = emailRequest.toByteArray();
        streamBridge.send(EMAIL_REQUEST_QUEUE, message);
    
        log.info("Message sent to the queue");
    }
    

    此 Java 示例用于 StreamBridge 异步发送消息。 此方法可确保主应用程序保持响应状态,并且可以同时处理其他任务,同时分离服务以可管理的速度处理排队的请求。

  • 实现消息重试和删除。 实现一种机制,以便重试处理无法成功处理的队列消息。 如果故障持续存在,则应从队列中删除这些消息。 例如,服务总线具有内置的重试和死信队列功能。

  • 配置幂等消息处理。 处理来自队列的消息的逻辑必须是幂等的,才能处理消息可能多次处理的情况。 在 Spring Boot 中,可以使用 @StreamListener@KafkaListener 具有唯一消息标识符来防止重复处理。 或者,可以组织业务流程以使用 Spring Cloud Stream 在功能方法中运行,其中该方法 consume 是在重复运行时以生成相同结果的方式定义的。 有关管理消息消耗行为的设置列表,请参阅 具有服务总线的 Spring Cloud Stream。

  • 管理对用户体验的更改。 使用异步处理时,任务可能不会立即完成。 若要设置预期并避免混淆,请确保用户知道何时仍在处理其任务。 使用视觉提示或消息来指明任务正在进行中。 让用户选择在任务完成时接收通知,如电子邮件或推送通知。

实现使用者竞争模式

分离服务中实现竞争使用者模式 ,以管理来自消息队列的传入任务。 这种模式涉及在已分离服务的多个实例之间分配任务。 这些服务处理来自队列的消息。 该模式增强了负载均衡,并增加了系统处理同时请求的容量。 使用者竞争模式在以下情况下有效:

  • 消息处理的顺序并不重要。
  • 队列不受格式错误消息的影响。
  • 处理操作是幂等的,这意味着它可以多次应用,而无需在初始应用程序后更改结果。

要实现使用者竞争模式,请遵循以下建议:

  • 处理并发消息。 当服务从队列接收消息时,通过配置并发来匹配系统设计,确保系统可预测地进行缩放。 负载测试结果可以帮助你确定要处理的并发消息数。 可以从一个开始来衡量系统执行方式。

  • 禁用预提取。 禁用消息预提取,以便使用者仅在消息准备就绪时提取消息。

  • 使用可靠的消息处理模式。 使用可靠的处理模式(如 Peek-Lock),自动重试处理失败的消息。 此模式比删除优先方法更可靠。 如果一个工作线程无法处理消息,则另一个工作线程必须能够无差错地处理该消息,即使该消息已被多次处理。

  • 实现错误处理。 将格式不正确或无法处理的消息路由到单独的死信队列。 这种设计可避免重复处理。 例如,可以在消息处理过程中捕获异常,并将有问题的消息移动到单独的队列。 使用服务总线时,消息会在指定的传递尝试次数或应用程序显式拒绝后移动到死机队列。

  • 处理失序消息。 设计使用者以处理未按顺序送达的消息。 如果有多个并行使用者,它们可能会无序处理消息。

  • 根据队列长度进行缩放。 使用来自队列的消息的服务应根据队列长度自动缩放。 基于缩放的自动缩放可有效处理传入消息的峰值。

  • 使用消息回复队列。 如果系统需要消息后处理通知,请设置专用回复或响应队列。 此设置将操作消息传送与通知进程分开。

  • 使用无状态服务。 考虑使用无状态服务来处理队列中的请求。 这样做可实现轻松缩放和高效的资源使用。

  • 配置日志记录。 在消息处理工作流中集成日志记录和特定异常处理。 专注于捕获序列化错误并将这些有问题的消息定向到死信机制。 这些日志为故障排除提供了宝贵的见解。

引用实现对在容器应用中运行的无状态服务使用竞争使用者模式来处理来自服务总线队列的电子邮件传递请求。

处理器记录消息处理详细信息,以帮助进行故障排除和监视。 它捕获反序列化错误,并提供在调试期间非常有用的见解。 服务在容器级别进行缩放,以便基于队列长度高效处理消息峰值。 下面是代码:

@Configuration
public class EmailProcessor {

    private static final Logger log = LoggerFactory.getLogger(EmailProcessor.class);

    @Bean
    Function<byte[], byte[]> consume() {
        return message -> {

            log.info("New message received");

            try {
                EmailRequest emailRequest = EmailRequest.parseFrom(message);
                log.info("EmailRequest: {}", emailRequest);

                EmailResponse emailResponse = EmailResponse.newBuilder()
                        .setEmailAddress(emailRequest.getEmailAddress())
                        .setUrlToManual(emailRequest.getUrlToManual())
                        .setRequestId(emailRequest.getRequestId())
                        .setMessage("Email sent to " + emailRequest.getEmailAddress() + " with URL to manual " + emailRequest.getUrlToManual())
                        .setStatus(Status.SUCCESS)
                        .build();

                return emailResponse.toByteArray();

            } catch (InvalidProtocolBufferException e) {
                throw new RuntimeException("Error parsing email request message", e);
            }
        };
    }
}

实现运行状况终结点监视模式。

在主应用代码和已分离服务代码中实现运行状况终结点监视模式,以跟踪应用程序终结点的运行状况。 AKS 或容器应用等业务流程协调程序可以轮询这些终结点,以验证服务运行状况并重启不正常的实例。 Spring Boot 执行器为运行状况检查提供内置支持。 它可以公开密钥依赖项(如数据库、消息代理和存储系统)的运行状况检查终结点。 要实现运行状况终结点监控模式,请遵循以下建议:

  • 实现运行状况检查。 使用 Spring Boot 执行器提供运行状况检查终结点。 执行器公开 /actuator/health 终结点,其中包括内置运行状况指示器和针对各种依赖项的自定义检查。 若要启用运行状况终结点,请在 pom.xmlbuild.gradle 文件中添加 spring-boot-starter-actuator 依赖项:

    <!-- Add Spring Boot Actuator dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    

    application.properties 中配置运行状况终结点,如参考实现中所示:

        management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
    
  • 验证依赖项。 Spring Boot 执行器包括各种依赖项(例如数据库、消息代理(RabbitMQ 或 Kafka)和存储服务的运行状况指示器。 若要验证 Azure Blob 存储或服务总线等 Azure 服务的可用性,请使用 Azure Spring Apps 或 Micrometer 等技术,这些技术为这些服务提供运行状况指标。 如果需要自定义检查,可以通过创建自定义 HealthIndicator 豆来实现这些检查:

    import org.springframework.boot.actuate.health.Health;
    import org.springframework.boot.actuate.health.HealthIndicator;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomAzureServiceBusHealthIndicator implements HealthIndicator {
        @Override
        public Health health() {
            // Implement your health check logic here (for example, ping Service Bus).
            boolean isServiceBusHealthy = checkServiceBusHealth();
            return isServiceBusHealthy ? Health.up().build() : Health.down().build();
        }
    
        private boolean checkServiceBusHealth() {
            // Implement health check logic (pinging or connecting to the service).
            return true; // Placeholder. Implement the actual logic.
        }
    }
    
  • 配置 Azure 资源。 将 Azure 资源配置为使用应用的运行状况检查 URL 来确认实时性和就绪性。 例如,可以使用 Terraform 来确认部署到容器应用的应用的实时性和就绪性。 有关详细信息,请参阅 容器应用中的运行状况探测。

实现重试模式

重试模式 使应用程序能够从暂时性故障中恢复。 此模式是可靠 Web 应用模式的核心,因此 Web 应用应已使用重试模式。 将重试模式应用于消息传送系统和从 Web 应用中提取的分离服务发出的请求。 要实现重试模式,请遵循以下建议:

  • 配置重试选项。 请务必配置负责与消息队列交互的客户端,并设置适当的重试设置。 指定参数,例如最大重试次数、重试之间的延迟和最大延迟。

  • 使用指数退避。 为重试尝试实现指数退避策略。 此策略涉及增加每次重试之间的时间,这有助于在高故障率期间减少系统上的负载。

  • 使用 SDK 重试功能。 对于具有专用 SDK(例如服务总线或 Blob 存储)的服务,请使用内置的重试机制。 这些内置机制针对服务的典型用例进行优化,可以更有效地处理重试,并且需要更少的配置。

  • 对 HTTP 客户端使用标准复原库。 对于 HTTP 客户端,可以将 Resilience4j 与 Spring 的 RestTemplate 或 WebClient 结合使用来处理 HTTP 通信中的重试。 可以使用 Resilience4j 的重试逻辑包装 RestTemplate,以有效处理暂时性 HTTP 错误。

  • 处理消息锁定。 对于基于消息的系统,实现支持在不丢失数据丢失的情况下重试的消息处理策略。 例如,当它们可用时,请使用速览锁模式。 确保有效重试失败的消息,并在重复失败后将其移至死信队列。

配置指南

以下部分提供有关实现配置更新的指导。 每个部分都与 Well-Architected 框架的一个或多个支柱对齐。

配置 可靠性 (RE) 安全性 (SE) 成本优化 (CO) 卓越运营 (OE) 性能效率 (PE) 支持架构良好的框架原则
配置身份验证和授权 SE:05
OE:10
实现独立的自动缩放 RE:06
CO:12
PE:05
容器化服务部署 CO:13
PE:09
PE:03

配置身份验证和授权

若要在添加到 Web 应用的任何新 Azure 服务(工作负荷标识)上配置身份验证和授权,请遵循以下建议:

  • 为每项新服务使用托管标识。 每项独立服务都应有自己的标识,并使用托管标识进行服务间身份验证。 托管标识无需在代码中管理凭据,同时还降低了凭据泄露的风险。 它们有助于避免在代码或配置文件中包含敏感信息,例如连接字符串。

  • 为每个新服务授予最低权限。 只为每个新服务标识分配必要的权限。 例如,如果标识只需要推送到容器注册表,请不要向其授予拉取权限。 定期查看这些权限,并根据需要对其进行调整。 为不同角色(如部署和应用程序)使用不同的标识。 这样做会限制一个标识遭到入侵时的潜在损害。

  • 使用基础结构即代码(IaC)。 使用 Bicep 或类似的 IaC 工具(如 Terraform)来定义和管理云资源。 IaC 确保部署中安全配置的一致应用程序,并使你能够对基础结构设置进行版本控制。

要配置用户(用户标识)的身份验证和授权,请遵循以下建议:

  • 授予用户最低权限。 与服务一样,请确保用户只有执行其任务所需的权限。 定期查看和调整这些权限。

  • 定期进行安全审核。 定期查看和审核安全设置。 查找错误配置和不必要的权限,并立即纠正或删除它们。

参考实现使用 IaC 为添加的服务分配托管标识,并为每个标识分配特定角色。 它通过定义容器注册表推送和拉取的角色来定义部署的角色和权限访问权限。 下面是代码:

resource "azurerm_role_assignment" "container_app_acr_pull" {
  principal_id         = var.aca_identity_principal_id
  role_definition_name = "AcrPull"
  scope                = azurerm_container_registry.acr.id
}

resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
  name                = "ContainerRegistryUserAssignedIdentity"
  resource_group_name = var.resource_group
  location            = var.location
}

resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
}


# For demo purposes, allow the current user to access the container registry.
# Note: When running as a service principal, this is also needed.
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "Contributor"
  principal_id         = data.azuread_client_config.current.object_id
}

配置独立的自动缩放

新式 Web 应用模式开始分解整体体系结构并引入服务分离。 在 Web 应用体系结构分离后,可以独立缩放已分离服务。 缩放 Azure 服务以支持独立的 Web 应用程序服务,而不是整个 Web 应用,在满足需求的同时优化了缩放成本。 要自动缩放容器,请遵循以下建议:

  • 使用无状态服务。 确保服务无状态。 如果 Web 应用包含进程内会话状态,请将其外部化到分布式缓存(如 Redis)或 SQL Server 等数据库。

  • 配置自动缩放规则。 使用自动缩放配置,对服务进行最具成本效益的控制。 对于容器化服务,基于事件的缩放(如 Kubernetes Event-Driven 自动缩放程序(KEDA)通常提供精细控制,使你能够基于事件指标进行缩放。 容器应用 和 AKS 支持 KEDA。 对于不支持 KEDA 的服务(如 App 服务),请使用平台本身提供的自动缩放功能。 这些功能通常包括根据基于指标的规则或 HTTP 流量进行缩放。

  • 配置最小副本。 若要防止冷启动,请配置自动缩放设置以保持至少一个副本。 冷启动是从停止状态初始化服务。 冷启动通常会延迟响应。 如果最小化成本是一个优先事项,并且可以容忍冷启动延迟,请在配置自动缩放时将最小副本计数设置为 0。

  • 配置冷却期。 应用适当的冷却期,在缩放事件之间引入延迟。 目标是防止临时负荷高峰所触发的过度缩放活动。

  • 配置基于队列的缩放。 如果应用程序使用服务总线等消息队列,请将自动缩放设置配置为根据请求消息队列的长度进行缩放。 缩放程序尝试维护队列中每个 N 消息的服务副本(向上舍入)。

例如,参考实现使用 服务总线 KEDA 缩放程序根据服务总线队列的长度自动缩放容器应用。 缩放规则(名为 service-bus-queue-length-rule)根据指定服务总线队列中的消息计数调整服务副本数。 messageCount 参数设置为 10,它将缩放程序配置为为队列中每 10 条消息添加一个副本。 最大副本计数(max_replicas)设置为 10。 除非它被重写,否则最小副本计数为隐式 0。 此配置允许服务在队列中没有消息时缩减到零。 服务总线队列的连接字符串作为机密存储在 Azure 中,名为 azure-servicebus-connection-string,用于向服务总线验证缩放程序。 下面是 Terraform 代码:

    max_replicas = 10
    min_replicas = 1

    custom_scale_rule {
      name             = "service-bus-queue-length-rule"
      custom_rule_type = "azure-servicebus"
      metadata = {
        messageCount = 10
        namespace    = var.servicebus_namespace
        queueName    = var.email_request_queue_name
      }
      authentication {
        secret_name       = "azure-servicebus-connection-string"
        trigger_parameter = "connection"
      }
    }

容器化服务部署

容器化是应用在可可靠地部署到各种主机的轻型映像中所需的所有依赖项的封装。 要实现容器化部署,请遵循以下建议:

  • 标识域边界。 首先在整体应用程序中标识域边界。 这样做有助于确定可以提取到单独的服务中的应用程序的各个部分。

  • 创建 Docker 映像。 为 Java 服务创建 Docker 映像时,请使用官方的 OpenJDK 基础映像。 这些映像仅包含 Java 需要运行的最小包集。 使用这些图像可最大程度地减少包大小和攻击外围应用。

  • 使用多阶段 Dockerfile。 使用多阶段 Dockerfile 将生成时资产与运行时容器映像分开。 使用此类型的文件有助于保持生产映像的较小且安全。 还可以使用预配置的生成服务器并将 JAR 文件复制到容器映像中。

  • 以非根用户身份运行。 以非根用户身份运行 Java 容器(通过用户名或 UID $APP_UID),以符合最低特权原则。 这样做会限制已泄露容器的潜在影响。

  • 侦听端口 8080。 以非根用户身份运行容器时,请将应用程序配置为侦听端口 8080。 这是非根用户的常见约定。

  • 封装依赖项。 确保应用所需的所有依赖项都封装在 Docker 容器映像中。 通过封装,应用就能可靠地部署到各种主机上。

  • 选择正确的基础映像。 所选的基础映像取决于部署环境。 例如,如果部署到容器应用,则需要使用 Linux Docker 映像。

参考实现演示用于容器化 Java 应用程序的 Docker 生成过程。 Dockerfile 使用具有 OpenJDK 基础映像(mcr.microsoft.com/openjdk/jdk:17-ubuntu)的单阶段生成,该映像提供了必要的 Java 运行时环境。

Dockerfile 包括以下步骤:

  1. 声明卷。 定义了临时卷(/tmp)。 此卷提供与容器的主文件系统分开的临时文件存储。
  2. 复制项目。 应用程序的 JAR 文件(email-processor.jar)将复制到容器中,以及用于监视的 Application Insights 代理(applicationinsights-agent.jar)。
  3. 设置入口点。 容器配置为运行启用了 Application Insights 代理的应用程序。 代码使用 java -javaagent 在运行时监视应用程序。

Dockerfile 仅通过包括运行时依赖项来缩小映像。 它适用于支持基于 Linux 的容器容器的容器应用等部署环境。

# Use the OpenJDK 17 base image on Ubuntu as the foundation.
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu

# Define a volume to allow temporary files to be stored separately from the container's main file system.
VOLUME /tmp

# Copy the packaged JAR file into the container.
COPY target/email-processor.jar app.jar

# Copy the Application Insights agent for monitoring.
COPY target/agent/applicationinsights-agent.jar applicationinsights-agent.jar

# Set the entrypoint to run the application with the Application Insights agent.
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]

部署参考实现

部署适用于 Java 的新式 Web 应用模式的参考实现。 存储库中提供了开发和生产部署的说明。 部署实现后,可以模拟和观察设计模式。

下图显示了参考实现的体系结构:

显示参考实现体系结构的 关系图。

下载此体系结构的 Visio 文件