微服务架构已成为现代应用开发的事实上的选择。尽管它解决了许多问题,但它并不是万能的。与所有软件一样,它有自己独特的一组挑战需要解决。这就需要学习微服务中的常见设计模式,并通过可重用的解决方案来解决这些挑战。
在深入探讨设计模式之前,了解微服务架构所建立的核心原则非常重要:
图1*: 微服务架构核心实践*
应用这些原则通常会带来一些挑战和问题。在本文中,我们将分解关键的微服务模式、它们解决的常见问题以及它们提供的解决方案。
本指南可以作为构建微服务架构时的重要参考,帮助您有效地设计系统。让我们开始吧。
微服务的核心是使服务松散耦合,从而应用单一职责原则。然而,将一个应用程序分解成更小的部分必须是逻辑性的。我们如何将一个应用程序分解成小服务?
一种策略是按业务能力分解。业务能力是企业为创造价值而进行的一项活动。给定业务的能力集取决于业务类型。例如,保险公司的能力通常包括销售、营销、承保、理赔处理、账单、合规等。每个业务能力可以被视为一个服务——只是它是面向业务的而不是技术性的。
按业务能力分解应用程序可能是一个好的开始,但你会遇到所谓的"上帝类",这些类在多个服务之间是通用的,难以分解。例如,Order
类将在订单管理、订单接收、订单交付等多个服务中使用。我们如何分解它们?
对于"上帝类"的问题,领域驱动设计(DDD)可以提供帮助。DDD使用子域和有界上下文概念来解决这个问题。DDD将为企业创建的整个领域模型分解为子域。每个子域都有一个模型,该模型的范围称为有界上下文。每个微服务将围绕有界上下文进行开发。
注意: 识别子域并非易事,需要对业务有深入了解。像业务能力一样,通过分析业务及其组织结构并识别不同领域来确定子域。
到目前为止,我们讨论的设计模式都是针对新建项目(greenfield)进行分解,但我们大多数工作是针对现有的大型单体应用程序(brownfield)。将上述所有设计模式应用于这些大型单体应用程序将非常困难——在使用过程中将它们拆分成更小的部分是一项艰巨的任务。
绞杀者模式可以提供帮助。绞杀者模式基于一种类似葡萄藤缠绕树木并逐渐“扼杀”它们的比喻。这种解决方案非常适用于Web应用程序,其中调用会往返,对于每个URI调用,可以将一个服务拆分成不同领域并作为独立服务托管。这个想法是一次处理一个领域。这会在相同URI空间中创建两个独立的应用程序并存。最终,新重构的应用程序“扼杀”或替换原始应用程序,直到最终可以关闭单体应用程序。
当一个应用程序被拆分成更小的微服务时,有几个问题需要解决:
API网关有助于解决许多由于实施微服务而产生的问题,不仅限于上述问题:
5. API网关是任何微服务调用的唯一入口点。
6. 它可以作为代理服务,将请求路由到相关微服务,抽象出生产者细节。
7. 它可以将请求分发到多个服务并聚合结果后发送回消费者。
8. 通用API无法满足所有消费者需求;此解决方案可以为每种特定类型客户端创建细粒度API。
9. 它还可以将协议请求(例如AMQP)转换为另一个协议(例如HTTP),反之亦然,以便生产者和消费者能够处理。
10. 它还可以卸载微服务的身份验证/授权责任。
如上所述,在API网关模式下,我们面临聚合数据的问题。当将业务功能拆分成几个较小逻辑代码段时,有必要考虑如何协作每个服务返回的数据。这一责任不能留给消费者,因为这样可能需要了解生产者应用程序的内部实现。
聚合器模式有助于解决这个问题。它讨论了如何从不同服务聚合数据,然后发送最终响应给消费者。这可以通过两种方式完成:
11. 组合微服务 将调用所有必需的微服务,整合数据并在发送回之前转换数据。
12. API网关 也可以将请求划分给多个微服务,并在发送给消费者之前聚合数据。
建议如果要应用任何业务逻辑,则选择组合微服务。否则,API网关是公认的解决方案。
当通过分解业务能力/子域开发服务时,负责用户体验的服务必须从多个微服务中提取数据。在单体架构中,通常只有一个从UI到后端服务的调用来检索所有数据并刷新/提交UI页面。然而,现在情况不再如此,因此我们需要了解如何做到这一点。
对于微服务,UI必须设计成具有多个部分/区域的骨架,每个部分将调用单个后端微服务以提取数据。这被称为针对服务的UI组件组合。像AngularJS和ReactJS这样的框架可以轻松实现这一点。这些屏幕被称为单页应用程序(SPA)。这使得应用程序能够刷新屏幕的特定区域而不是整个页面。
团队经常面临如何为微服务定义其数据库架构的挑战。以下是必须解决的问题:
#### **解决方案**
要解决上述问题,必须设计每个微服务一个数据库。它必须是该服务专用的,并且只能通过该微服务API访问。不能直接由其他服务访问。
例如,对于关系型数据库,我们可以使用每个服务私有表、每个服务一个模式或每个服务一个数据库服务器。每个微服务应该有一个单独的数据库ID,以便可以提供单独的访问权限,设置屏障并防止它使用其他服务的表。

***图2****: 每个服务一个数据库架构*
### 模式 #8: 每个服务共享数据库
#### **问题**
我们已经讨论了每个服务一个数据库是理想的微服务,但这仅在应用程序是新建项目且采用DDD时才可能。如果应用程序是单体应用并试图拆分成微服务,非规范化并不容易。那么在这种情况下最合适的架构是什么?
#### **解决方案**
每个服务共享数据库不是理想的,但这是上述场景中的可行解决方案。大多数人认为这是微服务的反模式,但对于现有项目,这是将应用程序拆分成更小逻辑部分的良好起点。
在这种模式中,一个数据库可以与多个微服务对齐,但必须限制在2-3个,否则扩展、自主性和独立性将难以执行:

***图3****: 共享数据库架构*
### 模式 #9: 命令查询责任分离(CQRS)
#### **问题**
一旦我们实施了每个服务一个数据库,就需要查询,这需要从多个服务联合数据——这是不可能的。那么,我们如何在微服务架构中实现查询?
#### **解决方案**
CQRS建议将应用程序拆分成两个部分——命令端和查询端。命令端处理`Create`、`Update`和`Delete`请求。查询端通过使用物化视图处理查询组件。通常与事件溯源设计模式一起使用,以创建任何数据更改的事件。因此,通过订阅事件流来保持物化视图最新。
### 模式 #10: Saga模式
#### **问题**
当每个服务都有自己的数据库且业务事务跨越多个服务时,我们如何确保跨服务的数据一致性?例如,对于电子商务应用程序,其中客户有信用额度,应用程序必须确保新订单不会超出客户的信用额度。由于订单和客户在不同的数据库中,应用程序不能简单地使用本地ACID事务。
#### **解决方案**
Saga代表由多个子请求组成的高级业务流程,每个子请求在单一服务内更新数据。每个请求都有一个补偿请求,在请求失败时执行。它可以通过两种方式实现:
## 可观测性模式
接下来,让我们深入探讨微服务可观测性模式。下面是一个用于参考所有可观测性主题的示例微服务架构图。

***图4****: 微服务可观测性* *图(来源:* [*CNCF*](https://www.cncf.io/blog/2023/11/02/opentelemetry-in-decoupled-event-driven-architectures-solving-for-the-black-box-when-your-consuming-applications-are-constantly-changing/)*)*
### 模式 #11: 日志聚合
#### **问题**
考虑一个应用程序由运行在多台机器上的多个服务实例组成。请求通常跨越多个服务实例。每个服务实例生成标准格式的日志文件。我们如何通过日志了解特定请求的应用程序行为?
#### **解决方案**
我们需要一个集中式日志记录服务,将每个服务实例的日志聚合起来。用户可以搜索和分析日志。他们可以配置在日志中出现某些消息时触发的警报。例如,PCF具有Loggeregator,它收集PCF平台(路由器、控制器、diego等)以及应用程序中的每个组件的日志。AWS Cloud Watch也有类似功能。
### 模式 #12: 性能指标
#### **问题**
当由于微服务架构增加了多个服务组合时,监控事务变得至关重要,以便在出现问题时发送警报。那么我们应如何收集指标以监控应用程序性能?
#### **解决方案**
需要一个指标服务来收集有关各项操作的统计信息。它应该聚合应用程序服务的指标,提供报告和警报。有两种聚合指标模型:
推送——该服务将指标推送到指标服务,例如New Relic、AppDynamics等。
拉取——指标服务器从该服务拉取指标,例如Prometheus。
在微服务架构中,请求通常跨越多个服务。每个服务通过执行一个或多个操作来处理请求。那么,我们如何端到端地追踪一个请求以排除故障?
我们需要一个服务来:
为每个外部请求分配一个唯一的外部请求ID。
将外部请求ID传递给所有服务。
在所有日志消息中包含外部请求ID。
记录关于请求和操作的信息(例如,开始时间、结束时间等),并将其存储在集中式服务中。
Spring Cloud Sleuth与Zipkin服务器是一个常见的实现示例。
当实施微服务架构时,可能会出现服务已启动但无法处理事务的情况。在这种情况下,如何确保请求不会发送到这些失败的实例?我们可以通过负载均衡模式来解决这个问题。
每个服务需要有一个端点用于检查应用程序的健康状况,例如/health
。这个API应该检查主机状态、与其他服务/基础设施的连接以及任何特定逻辑。
Spring Boot Actuator实现了/health
端点,并且可以自定义实现。
一个服务通常会调用其他服务和数据库。对于每个环境(如开发、QA、UAT或生产),端点URL或其他配置属性可能不同。任何这些属性的更改可能需要重新构建和重新部署服务。我们如何避免代码修改以进行配置更改?
外部化配置,包括端点URL和凭证,将解决这个问题。应用程序应该在启动时或动态加载它们。
Spring Cloud配置服务器提供了将属性外部化到GitHub并作为环境属性加载的选项。这些可以在应用程序启动时访问,也可以在不重启服务器的情况下刷新。
当微服务出现时,我们需要解决几个关于调用服务的问题:
需要创建一个服务注册表,它将记录每个生产者服务的元数据。服务实例在启动时应注册到注册表,并在关闭时取消注册。因此,消费者或路由器应查询注册表以查找服务的位置。
注册表还需要对生产者服务进行健康检查,以确保只有工作正常的服务实例可供使用。服务发现有两种类型:客户端发现和服务器端发现。客户端发现的一个例子是Netflix Eureka,服务器端发现的一个例子是AWS ALB。
一个服务通常会调用其他服务以检索数据,而下游服务可能会出现故障。这有两个问题:首先,请求将继续发送到故障的服务,耗尽网络资源并降低性能。其次,用户体验将变得不佳且不可预测。我们如何避免级联服务故障并优雅地处理故障?
消费者应通过代理调用远程服务,该代理行为类似于电路断路器。当连续故障次数超过阈值时,断路器会跳闸,并且在超时时间段内,所有尝试调用远程服务的请求将立即失败。在超时结束后,断路器允许有限数量的测试请求通过。如果这些请求成功,断路器恢复正常操作。否则,如果出现故障,则超时时间段重新开始。
Netflix Hystrix是断路器模式的良好实现。它还帮助定义回退机制,当断路器跳闸时可以使用。这提供了更好的用户体验。
使用微服务架构,一个应用程序可以有许多微服务。如果我们停止所有服务然后部署增强版本,停机时间可能很长且影响业务。此外,任何回滚都将是一场噩梦。那么我们如何避免或减少部署期间的停机时间?
蓝绿部署策略可以实现减少或消除停机时间。它通过运行两个相同的生产环境——蓝色和绿色——来实现这一点。假设绿色是现有的实时实例,而蓝色是应用程序的新版本。在任何时候,只有一个环境是实时的,并处理所有生产流量。所有云平台都提供了实现蓝绿部署的选项。
还有其他几个关键微服务架构模式,如sidecar模式、链式微服务、分支微服务、事件溯源设计模式、大使模式等等。随着微服务的发展,这个列表还在不断增长,因此我们很想听听您在评论中使用了哪些微服务模式!希望这些内容对您有所帮助!如果有任何问题或需要进一步的解释,请随时告诉我。