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

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

Azure 应用服务
Azure Front Door
用于 Redis 的 Azure 缓存

本文将介绍如何实现新式 Web 应用模式。 新式 Web 应用模式定义了如何在云中实现 Web 应用的现代化并引入面向服务的体系结构。 新式 Web 应用模式提供规范性的体系结构、代码和配置指南,这些指南符合 Azure 良好架构框架的原则,并基于 Reliable Web 应用模式进行构建。

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

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

如何实现新式 Web 应用模式

本文包含实现新式 Web 应用模式的体系结构、代码和配置指导。 使用以下链接导航到所需的指导:

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

提示

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

体系结构指南

新式 Web 应用模式建立在可靠 Web 应用模式的基础上。 它需要一些额外的体系结构组件来实现。 需要消息队列、容器平台、存储服务和容器注册表(请参阅图 1)。

显示了新式 Web 应用模式基线体系结构的示意图。图 1. 新式 Web 应用模式的基本体系结构要素。

为了实现更高的服务级别目标 (SLO),可以在 Web 应用体系结构中添加第二个区域。 将负载均衡器配置为将流量路由到第二个区域,以支持主动-主动或主动-被动配置,具体取决于业务需求。 这两个区域需要相同的服务,但一个区域有一个连接中心虚拟网络。 采用中心辐射型网络拓扑来集中和共享资源,如网络防火墙。 通过中心虚拟网络访问容器存储库。 如果有虚拟机,请在中心虚拟网络中添加一台堡垒主机,以便安全地管理虚拟机(请参阅图 2)。

显示了带有第二个区域和中心辐射型网络拓扑的新式 Web 应用模式体系结构的示意图。图 2. 带有第二个区域和中心辐射型网络拓扑的新式 Web 应用模式体系结构。

分离体系结构

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

  • 确定服务边界 应用域驱动的设计原则,以识别整体应用程序中的有限上下文。 每个边界上下文都代表一个逻辑边界,可以作为单独服务的候选对象。 代表不同业务功能且依赖项较少的服务适合进行分离。

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

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

  • 部署 Azure 服务。 选择并部署所需的 Azure 服务,以支持要提取的 Web 应用程序服务。 请参考以下选择正确的 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 应用 在 App 服务 上为容器选择 Web 应用,以获取最简单的 PaaS 体验。
  • 实现容器存储库。 使用任何基于容器的计算服务时,必须有一个存储库来存储容器映像。 可以使用 Docker 中心等公共容器注册表或 Azure 容器注册表等托管注册表。 使用 Azure 中的容器注册表简介指导来帮助你做出决定。

代码指导

要成功分离和提取独立服务,需要使用以下设计模式更新 Web 应用代码:Strangler Fig 模式、基于队列的负载调节模式、使用者竞争模式、运行状况终结点监控模式和重试模式。

显示了新式 Web 应用模式体系结构中设计模式角色的示意图。图 3. 设计模式的角色。

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

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

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

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

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

每个设计模式都提供了与良好架构框架的一个或多个支柱保持一致的好处(请参阅下表)。

设计模式 实现位置 可靠性 (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,获取管理消息使用方式行为的设置的进一步列表。

  • 管理体验的变化。 异步处理可能导致任务无法立即完成。 当用户的任务仍在处理过程中时,应让用户知道,以设定正确的预期,避免混淆。 使用视觉提示或消息来指明任务正在进行中。 让用户选择在任务完成时接收通知,如电子邮件或推送通知。

实现使用者竞争模式

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

  • 消息处理的顺序并不重要。
  • 队列不受格式错误消息的影响。
  • 处理操作具有幂等性,这意味着它可以多次使用,而不会改变初次使用后的结果。

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

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

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

  • 使用可靠的消息处理模式。 使用可靠处理模式,如 PeekLock 或类似模式来自动重试处理失败的消息。 与删除优先方法相比,这种模式提高了可靠性。 如果一个工作线程无法处理消息,则另一个工作线程必须能够无差错地处理该消息,即使该消息已被多次处理。

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

  • 处理失序消息。 设计使用者以处理未按顺序送达的消息。 多个并行使用者意味着它们处理消息的顺序可能会被打乱。

  • 根据队列长度进行缩放。 使用队列中消息的服务应根据队列长度自动进行缩放。 基于缩放的自动缩放功能可高效应对传入消息的峰值。

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

  • 使用无状态服务。 考虑使用无状态服务来处理队列中的请求。 它允许轻松缩放和高效使用资源。

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

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

处理器记录消息处理详细信息,有助于进行故障排除和监视。 它捕获反序列化错误,并提供调试过程时所需的见解。 服务在容器级别缩放,允许基于队列长度高效处理消息峰值(请参阅以下代码)。

@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 执行器提供运行状况检查的内置支持,它可以公开密钥依赖项(如数据库、消息代理和存储系统)的运行状况检查终结点。 要实现运行状况终结点监控模式,请遵循以下建议:

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

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

    application.properties 参考实现中所示配置运行状况终结点: txt management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents

  • 验证依赖项。 Spring Boot 执行器包括各种依赖项(例如数据库、消息代理(RabbitMQ 或 Kafka)和存储服务的运行状况指示器。 若要验证 Azure 服务(如Azure Blob 存储或服务总线)的可用性,请使用 Azure Spring Apps 或 Micrometer 集成等社区插件,这些插件为这些服务提供运行状况指标。 如果需要自定义检查,可以通过创建自定义 HealthIndicator bean 来实现这些检查。

    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 (e.g., 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 actual logic
        }
    }
    
  • 配置 Azure 资源。 将 Azure 资源配置为使用应用的运行状况检查 URL 来确认实时性和就绪性。 例如,可以使用 Terraform 使用运行状况检查 URL 来确认部署到容器应用的应用的实时性和就绪性。 有关详细信息,请参阅 容器应用中的运行状况探测。

实现重试模式

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

  • 配置重试选项。 与消息队列集成时,确保为负责与队列交互的客户端配置适当的重试设置。 指定重试的最大次数、重试之间的延迟和最大延迟等参数。

  • 使用指数退避。 为重试尝试实现指数退避策略。 这意味着每次重试之间的时间将呈指数增长,这有助于在故障率较高时减轻系统的负荷。

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

  • 为 HTTP 客户端采用标准复原库。 对于 HTTP 客户端,可以使用 Resilience4* 以及 Spring 的 RestTemplate 或 WebClient 来处理 HTTP 通信中的重试。 Spring 的 RestTemplate 可以使用 Resilience4j 的重试逻辑进行包装,以有效处理暂时性 HTTP 错误。

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

配置指南

以下各部分将为实现配置更新提供指导。 每个部分都与“架构良好的框架”的一个或多个支柱相一致。

配置 可靠性 (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 current user access to 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 事件驱动自动缩放程序(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该机密用于向服务总线验证缩放程序。

    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。 使用多阶段 Dockerfiles 将生成时资产与运行时容器映像分开。 它有助于保持较小的生产映像并确保安全。 还可以使用预配置的生成服务器并将 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 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 entry point to run the application with the Application Insights agent
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]

部署参考实现

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

显示参考实现体系结构的关系图。图 3. 参考实现的体系结构。下载此体系结构的 Visio 文件