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

微服务体系结构样式

微服务是一种流行的体系结构类型,用于构建可复原、高度可缩放、可独立部署且能快速演变的应用程序。 构建成功的微服务体系结构需要从根本上转变思维模式。 它的意义不止于将应用分解为更小的服务。 还必须重新考虑系统的设计、部署和作方式。

微服务体系结构由一系列小型的自治服务组成。 每个服务都是自包含服务,并且应在边界上下文中实现单个业务功能。 边界上下文是业务内的自然划分,提供域模型所在的显式边界。

微服务体系结构样式的逻辑关系图。

什么是微服务?

微服务是小型、独立且松散耦合的组件,单个开发人员团队可以编写和维护这些组件。 每个服务都作为单独的代码库进行管理,使小型团队能够高效地处理它。 由于服务可以独立部署,团队可以更新现有服务,而无需重新生成或重新部署整个应用程序。 与具有集中式数据层的传统模型不同,微服务负责保留自己的数据或外部状态。 它们通过定义完善的 API 进行通信,使内部实现与其他服务保持隐藏。 此体系结构还支持 polyglot 编程,这意味着服务不需要共享相同的技术堆栈、库或框架。

组件

除了服务本身,其他组件还出现在典型的微服务体系结构中:

  • 管理或业务流程: 此管理组件处理微服务业务流程。 它跨节点计划并部署服务、检测故障、从故障中恢复,并按需启用自动缩放。 Kubernetes 等容器业务流程平台通常提供此功能。 在云原生环境中,Azure 容器应用等解决方案提供托管编排和自动扩展。 这些工具可减少部署复杂性和操作开销。

  • API 网关: API 网关充当客户端的入口点。 客户端将请求发送到 API 网关,而不是直接调用服务。 网关将这些请求转发到相应的后端服务。 它还处理交叉问题,例如身份验证、日志记录和负载均衡。 在云原生微服务体系结构中,Envoy 和 Nginx 等轻型服务代理支持内部服务到服务通信。 这种类型的内部流量(称为东西向流量)可实现高级路由和流量控制。

  • 面向消息的中间件: Apache Kafka 和 Azure 服务总线等消息传送平台通过促进松散耦合并支持高可伸缩性,在微服务中实现异步通信。 它们构成了事件驱动的体系结构的基础。 此方法允许服务实时响应事件,并通过异步消息传送进行通信。

  • 可观察性: 有效的可观测性策略可帮助团队保持系统可靠性和快速解决问题。 集中式日志记录将日志汇集在一起,以支持更轻松的诊断。 使用 OpenTelemetry 等应用程序性能监视代理和框架进行实时监视可查看系统运行状况和性能。 分布式跟踪跟踪跨服务边界的请求。 它可帮助团队找到瓶颈并提高性能。

  • 数据管理: 设计良好的数据库体系结构支持自治性和可伸缩性。 微服务通常通过根据每个服务的特定需求选择不同的数据库类型(例如 SQL 或 NoSQL)来使用 polyglot 持久性。 此方法与域驱动设计(DDD)和边界上下文的概念保持一致。 每个服务拥有其数据和架构。 此所有权可减少跨服务依赖关系,并允许服务独立发展。 这种分散式模型提高了灵活性、性能和系统复原能力。

优点

  • 敏捷: 由于微服务是独立部署的,因此更容易管理 bug 修复和功能发布。 可以在不重新部署整个应用程序的情况下更新服务,并在出现问题时回滚更新。 在许多传统应用程序中,如果在应用程序的一部分发现漏洞,它可能会影响整个发布过程。 例如,如果需要集成、测试和发布 bug 修复,bug 可能会停止新功能。

  • 小型、重点团队: 微服务应该足够小,单个功能团队可以生成、测试和部署微服务。 即使团队规模不大,也能大幅提升敏捷性。 大型团队的工作效率往往较低,因为沟通速度较慢,管理开销增加,敏捷性降低。

  • 小型基本代码: 在整体应用程序中,代码依赖项经常随着时间推移而纠缠。 添加新功能可能需要更改代码库的许多部分。 微服务体系结构通过不共享代码或数据存储来避免此问题。 此方法可最大程度地减少依赖项,并更轻松地引入新功能。

  • 混合技术: Teams 可以通过适当地使用技术堆栈组合来选取最适合其服务的技术。

  • 故障隔离: 如果单个微服务变得不可用,只要任何上游微服务旨在正确处理故障,它就不会中断整个应用程序。 例如,可以实现 断路器模式,也可以设计解决方案,以便微服务使用 异步消息传送模式相互通信。

  • 可伸缩性: 服务可以独立扩展。 此方法允许横向扩展需要更多资源的子系统,而无需横向扩展整个应用程序。 使用业务流程协调程序(如 Kubernetes)将更高的服务密度添加到单个主机上,这样就可以更高效地使用资源。

  • 数据隔离: 更新架构在微服务体系结构中更简单,因为只有一个微服务受到影响。 相比之下,整体式应用程序可能会使架构更改复杂化,因为多个组件通常与相同的数据交互。 此共享访问可能会造成任何修改的风险。

挑战

微服务的优点伴随着一定的权衡和妥协。 在创建微服务体系结构之前,请考虑以下难题:

  • 复杂性: 微服务应用程序具有比等效整体应用程序更多的移动部件。 每个服务更简单,但整个系统作为整体来说更复杂。 设计应用程序时,请确保考虑服务发现、数据一致性、事务管理和服务间通信等难题。

  • 开发和测试: 编写依赖于其他依赖服务的小型服务需要不同于编写传统的整体式或分层应用程序的方法。 现有工具并不总是设计为兼容服务依赖项。 跨服务边界进行重构可能很困难。 测试服务依赖项也非常困难,尤其是在应用程序发展迅速时。

  • 缺乏治理: 构建微服务的分散式方法具有优势,但也可能导致问题。 用户在生成过程中可能采用了许多不同的语言和框架,从而使应用程序变得难以维护。 设置一些项目范围的标准可能较为有用,同时不过分限制团队的灵活性。 此方法特别适用于跨切功能,例如日志记录。

  • 网络拥塞和延迟: 使用许多小型粒度服务可能会导致更多的服务间通信。 此外,如果服务依赖项链过长(服务 A 调用 B,它调用 C...),则额外的延迟可能会成为问题。 需要仔细设计 API。 避免过度聊天 API、考虑序列化格式,并查找使用异步通信模式(如 Queue-Based 负载调配模式)的位置。

  • 数据完整性: 每个微服务都负责自己的数据持久性。 因此,多个服务之间的数据一致性可能是一个挑战。 不同的服务在不同的时间、使用不同的技术持续提供数据,可能会取得不同程度的成功。 当多个微服务参与到保存新的或修改的数据中时,完整的数据更改不太可能被认为是原子性、一致性、隔离性和持久性(ACID)的事务。 相反,该技术更符合基本可用、软状态、最终一致性原则(BASE)。 如果可能,请采用最终一致性。

  • 管理: 成功的微服务体系结构需要成熟的 DevOps 文化。 跨服务的关联日志记录可能很难。 通常情况下,日志记录必须为单个用户操作关联多个服务调用。

  • 版本控制: 对服务的更新不得中断依赖于它的服务。 多个服务可在任意给定时间更新,因此,若不精心设计,可能会遇到向后或向前兼容性问题。

  • 技能集: 微服务是高度分布式系统。 请仔细评估团队是否具有成功使用微服务所需的技能和经验。

最佳做法

  • 围绕业务域对服务建模。 使用 DDD 界定有界上下文并定义明确的服务边界。 避免创建过于精细的服务,这可能会增加复杂性并降低性能。

  • 分散所有资源。 各个团队负责端到端设计和构建服务。 避免共享代码或数据架构。

  • 通过限制你使用的语言和框架数来标准化技术选择。 建立用于日志记录、监视和部署的平台范围标准。

  • 拥有数据的服务应当有专用的数据存储。 为每个服务和数据类型使用最合适的存储。

  • 服务通过设计完善的 API 进行通信。 避免泄露实现细节。 API 应对域建模,而不是对服务的内部实现建模。

  • 避免服务之间耦合。 耦合的原因包括共享的数据库架构和严格的通信协议。

  • 使用相互传输层安全性(mTLS)进行服务到服务加密来提高安全性。 实现基于角色的访问控制并使用 API 网关来强制实施策略。

  • 将跨领域问题(例如身份验证和安全套接字层终止)卸载到网关。 Dapr 等服务网格和框架也有助于解决常见的交叉问题,例如 mTLS 身份验证和复原能力。

  • 让网关不必了解域。 网关应处理和路由客户端请求,而无需了解业务规则或域逻辑。 否则,网关会变成一个从属物,从而导致服务之间耦合。

  • 服务应具有松散耦合和高功能内聚的特点。 应当将可能会一起更改的函数打包并部署在一起。 如果它们驻留在单独的服务中,这些服务最终会紧密耦合,因为一个服务中的更改需要更新另一个服务。 两个服务之间的通信过于频繁可能是紧密耦合和低内聚的征兆。

  • 使用持续集成和持续部署(CI/CD)管道自动执行测试和部署。 独立部署服务并监视推出运行状况。

  • 隔离故障。 使用复原策略可防止某个服务中的故障级联。 有关详细信息,请参阅 复原模式设计可靠应用程序

  • 使用混沌工程测试微服务体系结构及其依赖项的复原能力。 评估和改进系统如何处理部分故障。

  • 实现集中式日志记录、分布式跟踪(OpenTelemetry)和指标收集,以确保可观测性。

微服务的反模式

在设计和实现微服务时,通常会发生特定的陷阱,这会破坏此体系结构样式的优点。 识别这些反模式有助于团队避免成本高昂的错误,并构建更具弹性、可维护的系统。 避免以下反模式:

  • 在不深入了解业务域的情况下实现微服务会导致服务边界不协调并破坏预期优势。

  • 设计依赖于过去或未来事件的事件违反了原子和独立消息传送的原则。 此依赖项强制使用者等待并减少系统可靠性。

  • 将数据库实体用作事件会公开内部服务详细信息,并且往往无法传达正确的业务意图,这会导致紧密耦合和不明确的集成。

  • 不惜一切代价地避免数据重复,是一种反模式。 使用具体化视图等模式维护本地副本可提高服务自治性,并减少跨服务依赖关系。

  • 使用泛型事件会强制使用者解释和筛选消息。 此方法增加了不必要的复杂性,并降低了事件驱动通信的清晰度。

  • 在微服务之间共享通用库或依赖项会产生紧密耦合,这会使更改变得风险大,并违背自包含服务的原则。

  • 直接向使用者公开微服务会导致紧密耦合、可扩展性和安全风险。 使用 API 网关提供干净、可管理和安全入口点。

  • 将配置值保留在微服务内部,会将其与特定环境紧密耦合,从而使部署变得更加困难。 但是,外部化配置可提高灵活性和环境可移植性。

  • 直接在微服务中嵌入安全逻辑(如令牌验证)会使代码和维护复杂化。 或者,将安全性卸载到专用组件可使服务保持专注且更简洁。

  • 未能抽象常见微服务任务会导致重复、容易出错的代码并限制灵活性。 或者,使用 Dapr 等抽象框架通过将业务逻辑与基础结构问题分离来简化开发。

生成微服务体系结构

以下文章介绍了用于设计、构建和操作微服务架构的结构化方法。

使用域分析: 若要避免在设计微服务时出现常见陷阱,请使用域分析来定义微服务边界。 执行以下步骤:

  1. 使用域分析为微服务建模
  2. 使用策略 DDD 设计微服务
  3. 确定微服务边界

设计服务: 微服务需要分散且敏捷的方法来设计和生成应用程序。 有关详细信息,请参阅 设计微服务体系结构

在生产环境中运行: 由于微服务体系结构是分布式的,因此必须具有可靠的部署和监视作。