前言
Hello 大家好,我是l拉不拉米
,『超级架构师』
专栏很久没有更新了,最近一直在学习微服务有关的知识,今天特地从网络上整理了一些微服务架构中常用到的设计模式。全篇看完大概8-10分钟,一定可以让您对微服务有更深的了解。
微服务架构的现状
微服务架构已经成为现代应用开发的事实上的选择。虽然它解决了某些问题,但它不是万能的silver bullet(银弹)
。它有几个缺点,在使用这种架构时,必须解决许多问题。这导致需要学习这些问题中的常见模式并使用可重用的解决方案来解决它们。因此,需要讨论微服务的设计模式。在深入研究设计模式之前,我们需要了解微服务架构的构建原则:
- 可扩展性
- 可用性
- 弹性
- 独立、自主
- 去中心化治理
- 故障隔离
- 自动配置
- 通过
DevOps
持续交付
如果要应用这些原则,则在实际的开发交付过程中会带来一些挑战和具体的问题。我们需要关注的是如何通过微服务的设计模式来解决问题。
一、分解模式
1、按业务能力分解
问题
微服务就是让服务松散耦合,应用单一职责原则。然而,将应用程序分解成更小的部分必须符合逻辑。我们如何将应用程序分解为小服务?
解决
一种策略是按业务能力分解。
业务能力是企业为了创造价值而做的事情。给定业务的能力集
取决于业务类型。例如,保险公司的能力通常包括销售、营销、承保、理赔处理、计费、合规等。每个业务能力都可以被视为一种服务,这是一种面向业务的而不是面向技术的的分解模式。
2、按子域分解
问题
使用业务能力分解应用程序可能是一个好的开始,但您会遇到所谓的 God Classes(上帝类)
,并且常常并不容易分解。这些类将在多个服务中通用。例如:Order 类可以用于订单管理、接单、订单交付等,我们如何分解它们?
解决
对于 God Classes
的问题,使用 DDD(领域驱动设计)
可以有效的解决。它使用子域和限界上下文概念来解决这个问题。 DDD 将为企业创建的整个域模型分解为子域。每个子域都有一个模型,该模型的范围将称为限界上下文
。每个微服务都将围绕限界上下文开发。
注意:识别子域并非易事。它需要对业务的了解。与业务能力一样,子域是通过分析业务及其组织结构并确定不同的专业领域来确定的。
3、扼杀者模式
问题
到目前为止,我们所讨论的设计模式都是分解 未开发的应用程序(GreenField Applications),但是我们所做的工作中有 80% 是用于 已开发的应用程序(BrownField Applications) 中,这是个大型的整体应用程序。上述所有设计模式并不是适用于它们,因为把它们作为一个整体应用的同时将它们拆分成一个个较小的部分是一项艰巨的任务。
解决
扼杀者模式
可以解决此类问题。扼杀者模式是以缠绕类的藤蔓植物作为类比。该解决方案是与Web应用程序配合使用,在Web应用程序之间来回调用,对于每个URL的调用,一个服务可以分为不同的域并作为单独的服务托管。这个想法是一次做一个域,这将会创建两个单独的应用程序,它们并行存在于同一个URL空间中。最终,新重构的应用程序会“扼杀”或者替换原来的应用程序,直到最后可以停止整个应用程序。
二、集成模式
1、API网关模式
问题
当一个应用程序被分解为多个微服务时,还有一些问题需要解决:
- 如何调用多个微服务来抽象化生产者信息。
- 在不同设备上(比如台式机,移动设备和平板电脑),由于UI可能存在不同,应用程序需要不同的数据来响应相同的后端服务。
- 不同的使用者对于可重复使用的微服务响应格式可能不同,那由谁来进行数据转换或者字段操作。
- 生产者微服务可能不支持某些类型协议的处理方式。
解决
API网关有助于解决因微服务实现而引起的许多问题,而不仅限于上述问题。
API网关
是任务微服务调用的单一入口点。- 用作代理服务,将请求路由至相关的微服务,从而抽象出生产者的详细信息。
- 将一个请求发送至多个服务,然后把响应结果聚合后发送回消费者。
- 通用的
API网关
无法满足所有的消费者需求。可以为每种特定类型的客户端创建细粒度的API。 - 将协议请求(例如
AMQP
)转换成另外一个协议(例如HTTP
),反之亦然,以便生产者和消费者处理它。 - 它还可以减轻微服务的身份验证/授权责任。
2、聚合器模式
问题
我们已经讨论过如何解决API网关模式中的聚合数据问题。但是,接下来我们将会更加全面地讨论它。比如,在将业务功能分解为几个较小的逻辑代码时,有必要考虑聚合每个服务返回的数据。这个任务不能留给消费者,因为如果这么做消费者可能需要了解生产者应用程序的内部实现。
解决
聚合模式有助于解决此类问题。它讨论了关于如何聚合来自不同服务的数据,然后将最终响应结果发送给消费者。我们可以通过以下两种方式来实现:
- 复合微服务:将调用所有必需的微服务,整合数据,并在回退数据之前转换数据。
- API网关:将请求划分为多个微服务并聚合数据,然后再将其发送给使用者。
如果要应用任何业务逻辑,建议选择使用复合微服务。否则,API网关是已公认更好的解决方案。
3、客户端UI组合模式
问题
当通过分解业务功能/子域
来开发服务时,负责用户体验的服务必须从多个微服务中提取数据。在单体架构
的应用中,从UI到后端服务只有一次调用,以检索所有数据并刷新/提交UI页面。但是,微服务架构情况不一样了。我们需要了解如何去做。
解决
对于微服务,必须将UI设计为具有屏幕/页面的多个部分/区域的框架。每个部分都调用单个后端微服务以提取数据,这称为组合特定服务的UI组件。比如 AngularJS
和 ReactJS
之类的框架可以轻松地做到这一点。这些屏幕称为单页应用程序(SPA)。这使应用程序可以刷新屏幕的特定区域而不是刷新整个页面。
三、数据库模式
1、每个微服务一个数据库
问题
如何来定义微服务的数据库体系结构?让我们来看下需要解决的那些问题:
- 服务必须松耦合。它们可以独立开发,部署和扩展。
- 业务事务可能会强制跨越多个服务的不变量。
- 一些业务事务需要查询多个服务中的数据。
- 为了进行扩展有时必须对数据库进行复制和分片。
- 不同服务具有不同的数据存储要求。
解决
为了解决上述问题,必须为每个微服务设计一个数据库。它必须仅对该服务专用,只能由微服务API访问它,其他服务无法访问。 例如,对于关系型数据库,我们可以使用每个服务一个专用的表,每个服务一个 schema
或每个服务一个数据库服务器。每个微服务应具有一个单独的数据库ID,这样就可以提供单独的访问来设置一个隔离,防止它使用其他服务的表。
2、每个微服务共享数据库
问题
我们已经讨论了每个服务一个数据库是微服务的理想选择,这在应用程序开发前并且要使用 DDD
开发时,是可能实现的。但是,如果应用程序是一个整体并且试图尝试转型为微服务时,那么非规范化就不是那么容易了。在这种情况下合适的架构是什么呢?
解决
每个服务共享数据库不是理想的选择,但却是上述情况的可行解决方案。大多数人认为这是微服务的反模式,但这对于已开发的应用程序(BrownField Applications) 来说,是将应用程序分解成较小逻辑部分的一个很好的开始。这不适合未开发的应用程序(GreenField Applications)。在这种模式下,一个数据库可以与一个以上的微服务对齐,当然也不是无限制的,最多限制为2-3个微服务,否则扩展性,自主性和独立性将难以执行。
3、命令查询职责隔离(CQRS)
问题
一旦我们实现了每个服务的数据库,就肯定会有查询,而且这需要来自多个服务的联合数据——这是不可能的。那我们应该如何在微服务架构中实现查询呢?
解决
命令查询职责隔离(CQRS)
建议将应用程序分为两部分——命令端和查询端。
命令端负责处理创建,更新和删除请求。
查询端负责通过使用实例化视图来处理查询的部分。
通常将事件源模式
与CQRS
一起使用来为任何数据更改创建事件。通过订阅事件流,可以使实例化视图保持更新。
Saga模式
问题
当每个服务都有自己的数据库并且一个业务事物跨越多个服务时,我们该如何确保各个服务之间的数据一致性
呢?例如,对于客户有信用额度的电子商务应用程序,该应用程序必须确保新订单不会超过客户的信用额度。由于订单和客户位于不用的数据库中,因此应用程序不能简单地使用本地 ACID事务
。
解决
Saga相当于由几个子请求组成的高级业务流程,每个子请求在单个服务中更新数据。每个请求都会有一个补偿请求
,该请求在请求失败时执行。它通过两种方式实现:
- 编排(Choreography):当没有总协调时,每个服务都会生成和监听另一个服务时间,并决定是否应该采取行动。
- 编排器(Orchestration):负责一个Saga的决策和业务逻辑排序。
四、可观察模式
1、日志聚合
问题
一个应用程序由在多台服务器上运行的多个服务实例组成,请求通常跨越多个服务实例。每个服务实例均以标准化格式生成日志文件。我们该如何通过日志了解特定请求的应用程序行为呢?
解决
我们需要一个集中式日志记录服务,该服务可以汇总每个服务实例的日志。用户可以搜索和分析日志。他们可以配置某些消息出现在日志中时触发警报。例如,PCF(Pivotal Cloud Foundy)
确实具有 Loggeregator
,它从PCF平台的每个组件(路由器、控制器、diego等)以及应用程序中收集日志。AWS Cloud Watch
也做了同样的事情。
2、性能指标
问题
当使用微服务架构而导致的服务组合增加时,对事务进行监控就变得非常重要,以便在出现问题时可以监视模式并发送告警。我们应该如何收集指标来监控应用程序性能呢?
解决
需要一个度量服务
来收集关于单个操作的统计信息。它应该聚合提供报告和告警的应用程序服务的指标。聚合度量有两种模型:
- 推送(Push):服务将指标推送到指标服务,例如
NewRelic
,AppDynamics
- 提取(Pull):指标服务从服务中提取指标,例如
Prometheus
3、分布式跟踪
问题
在微服务架构中,请求通常跨越多个服务。每个服务通过跨多个服务执行一个或多个操作来处理请求。那么,我们如何跟踪端到端请求来解决问题呢?
解决 我们需要一项服务:
- 为每个外部请求分配一个唯一的外部请求ID
- 将外部请求ID传递给所有服务
- 在所有的日志消息中包含外部请求ID
- 记录有关请求和在集中服务中处理外部请求时执行的请求和操作的信息(例如,开始时间、结束时间)
Spring Cloud Slueth
、Zipkin server
、PinPoint
、SkyWalking
是比较主流的实现。
4、健康检查
问题
当实施了微服务架构时,服务可能会启动,但无法处理事务。在这种情况下,如何确保请求不会转到那些失败的实例呢?使用负载均衡模式实现。
解决
每个服务都需要有一个端点,可以用来检查应用程序的健康度,比如:/health
。这个API应该检查主机的状态、与其他服务/基础设施的连接以及任何特定的逻辑。
Spring Boot Actuator
实现了一个 /health
端点,并且该实现也可以自定义。
五、横切关注模式
1、外部配置
问题
服务通常也会调用其他服务和数据库。对于 Dev
、QA
、UAT
、prod
等每个环境,端点URL或某些配置属性可能不同。任何这些属性的更改都可能需要重新构建和重新部署服务。如何避免对配置更改进行代码修改呢?
解决
外部化所有配置,包括端点url和凭证。应用程序应该在启动或运行时加载它们。
Spring Cloud config server
提供了将属性外部化到 GitHub
并将其作为环境属性加载的选项。应用程序可以在启动时访问它们,也可以在不重启服务器的情况下刷新它们。
业界成熟的还有携程的Apollo
和阿里巴巴的Nacos
。
2、服务发现模式
问题
当微服务出现的时候,我们需要解决一些关于调用服务的问题:
- 使用容器技术,IP地址被动态分配给服务实例。每次地址更改时,使用者服务可能会中断,并需要手动更改。
- 每个服务的URL都必须被使用者记住并成为强耦合的。
那么消费者或者路由器如何知道所有可用的服务实例和位置呢?
解决
我们需要创建一个服务注册中心
,用来保存每个生产者服务的元数据
。服务实例在启动时注册到注册中心,在关闭时注销注册。 消费者或路由器查询注册表并找出服务的位置。 注册中心还需要对生产者服务进行健康检查,来确保只有服务的工作实例可以通过它使用。有两种类型的服务发现:客户端和服务器端。客户端注册发现的一个例子是 Netflix Eureka
,服务器端注册发现的一个例子是 AWS ALB
。
3、断路器模式
问题
服务通常会调用其他服务来检索数据,下游服务可能会出现故障。
这样做有两个问题:
- 请求将继续使用服务中断状态,耗尽网络资源并降低性能。
- 用户体验将是糟糕的和不可预测的。
如何避免级联服务故障并优雅地处理故障呢?
解决
消费者应该通过代理调用远程服务,代理的行为类似于断路器。当连续故障数超过阈值时,断路器跳闸,在超时期间,所有调用远程服务的尝试都会立即失败。超时过期后,断路器允许有限数量的测试请求通过。如果这些请求成功,断路器将恢复正常工作。否则,如果出现故障,超时周期将再次开始。
Netflix Hystrix
是断路器模式的良好实现。它还可以帮助你定义一个备用机制,可以使用断路器跳闸。这提供了更好的用户体验。
4、蓝绿部署模式
问题
使用微服务架构时,一个应用程序可以有许多微服务。假如我们停止所有服务,然后部署一个增强版本,停机时间将是巨大的,并可能会影响到业务。此外,回滚将是一场噩梦。我们如何避免或减少部署期间服务的停机时间呢?
解决
可以使用蓝绿部署
策略来减少或消除停机时间。它通过运行两个相同的生产环境(蓝色和绿色)来实现这一点。 让我们假设绿色是现有的活动实例,蓝色是应用程序的新版本。在任何时候,只有一个环境是运行状态, 该环境服务于所有生产流量。所有云平台都提供了实现蓝绿部署的选项。
总结
与微服务架构一起使用的还有许多其他模式,比如 Sidecar
、链式微服务
、分支微服务
、事件源模式
、持续交付模式
等等。随着我们在微服务方面获得更多的经验,这个名单还在不断增长。