《微服务架构设计模式》阅读笔记(六)

本文深入探讨了在微服务架构中实现安全性和可配置性的关键方面。从身份验证、访问授权到使用API网关处理安全问题,以及外部化配置和日志聚合等模式,阐述了设计安全、可观察和可配置服务的实践。同时,讨论了使用微服务基底和服务网格如何简化服务部署和管理。最后,提出了逐步重构单体应用至微服务的策略,如‘绞杀者’应用和垂直切片,强调了在重构过程中应避免大规模修改单体应用,以及如何处理数据库重构和数据一致性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第11章 开发面向生产环境的微服务应用

11.1 开发安全的服务

为了开发安全的软件并远离头条新闻,企业需要解决各种安全问题,包括硬件的物理安全性、传输和静态数据加密、身份验证、访问授权以及修补软件漏洞的策略,等等。
应用程序开发人员主要负责实现安全性的四个不同方面:

  • 身份验证:验证尝试访问应用程序的应用程序或人员(安全的术语叫主体)的身份。例如,应用程序通常会验证访问主体的凭据,例如用户的I和密码,或应用程序的AP密钥。
  • 访问授权:验证是否允许访问主体对指定数据完成请求的操作。应用程序通常使用基于角色的安全性和访问控制列表(ACL)的组合。基于角色的安全性为每个用户分配个或多个角色,授予他们调用特定操作的权限。ACL授予用户或角色对特定业务对象或聚合执行操作的权限。
  • 审计:跟踪用户在应用中执行的所有操作,以便检测安全问题,帮助客户实现并强制执行合规性。
  • 安全的进程间通信:理想情况下,所有进出服务的通信都应该采用传输层安全性
    (TLS)加密。服务间通信甚至可能需要使用身份验证。

11.1.1 传统单体应用程序的安全性。

如图11-1显示了单体FTGO应用程序的客户端如何验证和发出请求。
在这里插入图片描述

当用户使用其用户ID和密码登录时,客户端会向FTGO应用程序发出包含用户凭据的POST请求。FTGO应用程序验证凭据并将会话令牌返回给客户端。客户端在FTGO应用程序的每个后续请求中包含会话令牌。
安全架构的一个关键部分是会话,它存储主体的ID和角色。另一个关键是上线文,它存储有关发出当前请求的用户的信息。

11.1.2 在微服务架构中实现安全性

在微服务应用程序中实现安全性的一个挑战是我们不能仅仅从单体应用程序借鉴设计思路。这是因为单体应用程序的安全架构的一些方面对微服务架构来说是不可用的,例如:

  • 内存中的安全上下文:使用内存中的安全上下文(如Threadlocal)来传递用户身
    份。服务无法共享内存,因此它们无法使用内存中的安全上下文来传递用户身份。在微服务架构中,我们需要一种不同的机制来将用户身份从一个服务传递到另一个服务。
  • 集中会话:因为内存中的安全上下文没有意义,内存会话也没有意义。从理论上讲,多种服务可以访问基于数据库的会话,但它会违反松耦合的原则。我们需要在微服务架构中使用不同的会话机制。

由API Gateway处理身份验证
处理身份验证有两种不同的方法。一种选择是让各个服务分别对用户进行身份验证。这种方法的问题在于它允许未经身份验证的请求进入内部网络。它依赖于每个开发团队在所有服务中正确实现安全性。因此,出现安全漏洞的风险和概率都很大。
在服务中实现身份验证的另一个问题是不同的客户端以不同的方式进行身份验证。纯API客户端使用基本身份验证为每个请求提供凭据。其他客户端可能首先登录,然后为每个请求提供会话令牌。但我们要避免在服务中处理多种不同的身份验证机制。
更好的方法是让API Gateway在将请求转发给服务之前对其进行身份验证。在API
Gateway中进行集中API身份验证的优势在于只需要确保这里的验证是正确的。因此,出现安全漏洞的可能性要小得多。另一个好处是只有API Gateway需要处理各种不同的身份验证机制。这使得其他服务的实现变得简单了。
API Gateway调用的服务需要知道发出请求的主体(用户的身份)。它还必须验证请求是否已经过通过身份验证。解决方案是让API Gateway在每个服务请求中包含一个令牌。服务使用令牌验证请求,并获取有关主体的信息。API Gateway还可以为面向会话的客户端提供相同的令牌,以用作会话令牌。

处理访问授权
验证客户端的凭据很重要,但这还不够。应用程序还必须实现访问授权机制,已验证是否允许客户端执行所请求的操作。
实现访问授权的一个位置是API Gateway。例如,它可以将对GET/orders/{orderId}的访问限制为消费者和客户服务代表。如果不允许用户访问特定路径,则API Gateway可以在将请求转发到服务之前拒绝该请求。
在 API Gateway中实现访问授权的一个弊端是,它有可能产生API Gateway与服务之间的耦合,要求它们以同步的方式进行代码更新。而且,API Gateway通常只能实现对URL路径的基于角色的访问。由API Gateway实现对单个领域对象的访问授权通常是不实际的,因为这需要详细了解服务的领域逻辑。
另一个实现访问授权的位置是服务。服务可以对URL和服务方法实现基于角色的访问授权。它还可以实现ACL来管理对聚合的访问。例如,在Order service中可以实现基于角色和基于ACL的授权机制,以控制对Order的访问。

使用JWT传递用户身份和角色
在微服务架构中实现安全性时,你需要确定API Gateway应使用哪种类型的令牌来将用户信息传递给服务。有两种类型的令牌可供选择。一种选择是使用不透明(无可读性)的令牌,它们通常是一串UUID。不透明令牌的缺点是它们会降低性能和可用性,并增加延迟。因为这种令牌的接收方必须对安全服务发起同步RPC调用,以验证令牌并检索用户信息。
另一种消除对安全服务调用的方法是使用包含有关用户信息的透明令牌。透明令牌的个流行的标准是JSON Web令牌(JWT)。JWT是在访问双方之间安全地传递信息(例如用户身份和角色)的标准方式。JWT的内容包含一个JSON对象,其中有用户的信息,例如其身份和角色,以及其他元数据,如到期日期等。它使用仅为JWT的创建者所知的数字签名,例如API Gateway和JWT的接收者(服务)。该签名确保恶意第三方不能伪造或篡改JWT。
因为不需要再访问安全服务进行验证,JWT的一个问题是这个令牌是自包含的,也就是说它是不可撤销的。根据设计,服务将在验证JWT的签名和到期日期之后执行请求操作。因此,没有切实可行的方法来撤销落入恶意第三方手中的某个JWT令牌。解决方案是发布具有较短到期时间的JWT,这可以限制恶意方。但是,短期JWT的一个缺点是应用程序必须以某种方式不断重新发布JWT以保持会话活动。

在微服务架构中使用OAuth 2.0
OAuth 2.0是一种访问授权协议,最初旨在使公共云服务(如GitHub或Google)的用户能够授予第三方应用程序访问其信息的权限,而不必向第三方应用透露他们密码。
OAuth 2.0中的关键概念如下:

  • 授权服务器:提供用于验证用户身份以及获取访问令牌和刷新令牌的API。
  • 访问令牌(AccessToken):授予对资源服务器的访问权限的令牌。访问令牌的格式取决于具体的实现技术。
  • 刷新令牌(RefreshToken):客户端用于获取新的AccessToken的长效但同时也可被可撤销的令牌。
  • 资源服务器:使用访问令牌授权访问的服务。在微服务架构中,服务是资源服务器。
  • 客户端:想要访问资源服务器的客户端。在微服务架构中,API Gateway OAuth2.0客户端。

11.2 设计可配置的服务

外部化配置机制在运行时向服务实例提供配置属性值。主要有两种方法:

  • 推送模型:部署基础设施通过类似操作系统环境变量或配置文件,将配置属性传递给服务实例。
  • 拉取模型:服务实例从配置服务器读取它所需要的配置属性。

11.2.1 使用基于推送的外部化配置

推送模型依赖于部署环境和服务的协作。部署环境在创建服务实例时提供配置属性。如图11-7所示,它可能会将配置属性作为环境变量传递。或者,部署环境可以使用配置文件来提供配置属性。然后,服务实例在启动时读取配置属性。
在这里插入图片描述
推送模型是一种有效且广泛使用的配置服务的机制。它的一个限制是重新配置正在运行的服务可能很难,甚至不可能。部署基础设施可能不允许你在不重新启动服务的情况下更改正在运行的服务的外部化配置。例如,你无法更改正在运行的进程的环境变量。另一个限制是,配置属性值存在分散在众多服务定义中的风险。

11.2.2 使用基于拉取的外部化配置

在拉取模型中,服务实例从配置服务器读取其配置属性。图11-8显示了它的工作原理。
在这里插入图片描述
有多种方法可以实现配置服务器,包括:

  • 版本控制系统,如Git
  • SQL和NoSQL数据库
  • 专用配置服务器。

使用配置服务器有几个好处:

  • 集中配置:所有配置属性都存储在一个位置,这使它们更易于管理。此外,为了消除重复的配置属性,有些实现允许你定义全局默认值,针对单个服务的值可以覆盖这些默认值。
  • 敏感数据的透明解密:加密敏感数据(如数据库访问凭据)是一种安全性最佳实践。但是,使用加密的一个挑战是通常服务实例需要解密它们,这意味着它需要解密密钥。某些配置服务器实现会在将属性返回给服务之前自动对其进行解密。
  • 动态重新配置:服务可能会通过轮询等方式检测更新的属性值,并重新配置自身。

使用配置服务器的主要缺点是,除非由基础设施提供,否则它需要额外的人力进行设置

11.3 设计可观测的服务

假设你已将FTGO应用程序部署到生产环境中。你可能想知道应用程序正在做什么:每秒请求数和资源利用率,等等。如果出现问题,你还希望能收到告警,例如服务实例失败或磁盘写满,并且最好是在影响用户之前收到。而且,如果出现问题,你需要能够排除故障并确定根本原因。
在生产中管理应用程序的许多方面都超出了开发人员的职责范畴,例如监控硬件可用性和利用率。这些显然是运维的职责。但是,作为服务开发人员,你必须实现多种模式才能使你的服务更易于管理和排错。这些模式(如图11-9所示)公开了服务实例的行为和健康状况。它们使监控系统能够跟踪和可视化服务状态,并在岀现问题时生成告警。这些模式还可以更轻松地用于故障排错。
在这里插入图片描述
你可以使用以下模式来设计可观测的服务:

  • 健康检查API:公开返回服务运行状况的接口。
  • 日志聚合:记录服务活动并将日志写人集中式日志记录服务器,该服务器提供搜索和告警
  • 分布式跟踪:为每一个在服务之间跳转的外部请求分配唯一ID,并跟踪请求
  • 异常跟踪:向异常跟踪服务报告异常,该异常跟踪服务可以对异常进行重复数据删除,向开发人员发出警报并跟踪每个异常的解决方案。
  • 应用程序指标:服务运维指标,例如计数器和指标,并将它们公开给指标服务器。
  • 审核日志记录:记录用户操作。

大多数这些模式都有一个显著的特征,它们通常都有一个开发人员组件和一个运维人员组件。例如,健康检查API模式。开发人员负责确保其服务实现健康检查端点。运维人员负责定期调用健康检査API的监控系统。类似地,对于日志聚合模式,开发人员负责确保其服务记录有用的信息,而运维人员负责日志聚合。

11.3.1 使用健康检查API

服务实例需要能够告诉部署基础设施它是否能够处理请求。一个好的解决方案是服务实现健康检查接口,如图11-10。
在这里插入图片描述
使用健康检査时需要考虑两个问题。第一个是接口的实现,它必须报告服务实例的健康状况。第二个问题是如何配置部署基础设施以调用健康检査接口。

实现健康检查接口
实现健康检查接口的代码必须以某种方式确定服务实例的健康状况。一种简单的方法是验证服务实例是否可以访问其外部基础设施服务。具体做法取决于具体的基础设施服务,例如可以通过获取数据库连接,并执行测试查询来验证服务是否已连接到数据库。更复杂的方法是执行模拟客户端调用服务API的综合事务。这种健康检査更加彻底,但实现起来可能更耗时,执行时间也更长。

调用健康检查接口
如果没有人调用它,健康检査接口就没有意义了。部署服务时,必须配置部署基础设施以调用接口。如何执行此操作取决于部署基础设施的特定详细信息。

11.3.2 使用日志聚合模式

对于故障排除,日志是必不可少的。如果你想知道应用程序有什么问题,最好是从
阅读日志文件开始。但是在微服务架构中使用日志具有挑战性。例如,假设你正在调试GetOrderDetails()查询的问题。如第8章所述,FTGO应用程序使用API组合实现此查询。因此,你需要的日志条目分散在API Gateway的日志文件和多个服务中,包括Order Service和Kitchen Service。
解决方案是使用日志聚合。如图11-11所示,日志聚合流水线将所有服务实例的日志发送给集中式日志服务器。日志服务器存储日志后,你可以查看、搜索和分析日志。你还可以配置在日志中出现某些消息时触发的告警。
在这里插入图片描述

11.3.3 使用分布式追踪

深入了解应用程序正在执行的操作的一种好方法是使用分布式追踪。分布式追踪类似于单体应用程序中的性能分析器。它记录有关处理请求时所进行的服务树调用的信息(例如开始时间和结束时间)。然后,你可以看到服务如何在处理外部请求期间进行交互,包括花费时间的细分。
分布式追踪的一个有价值的副作用是它为每个外部请求分配一个唯一的ID。服务可以在其日志条目中包含请求ID。与日志聚合相结合时,请求ID使你可以轻松查找特定外部请求的所有日志条目。

11.3.4 使用应用程序指标模式

监控和告警功能是生产环境的关键部分。如图11-14所示,监控系统从技术栈的每个部分收集指标,这些指标提供有关应用程序健康状况的关键信息。指标涵盖的范围从基础设施相关的指标(如CPU、内存和磁盘利用率)到应用程序级别的指标(如服务请求延迟和执行的请求数)。
在这里插入图片描述
指标定期采样。指标样本具有以下三个属性:

  • 名称:指标的名称
  • 值:数值
  • 时间戳:样本的时间

此外,一些监控系统支持维度的概念,维度是任意的名称与值的组合。例如,报告jvm_memory_max_bytes的维度:area="heap",id=" PS Eden Space"和area="heap",id=" PS Old Gen"。维度通常用于提供其他信息,例如计算机名称、服务名称或服务实例
标识符。监控系统通常沿一个或多个维度聚合(求和或平均)度量样本。
监控的许多方面都是运维人员的职责。但是服务开发人员需要负责指标的两个方面。首先,他们必须编写监测服务的代码,以便收集有关其行为的指标。其次,他们必须将这些服务指标以及来自JVM和应用程序框架的指标发送给指标服务器。

11.3.5 使用异常追踪模式

查看异常的传统方法是查看日志。你甚至可以配置日志记录服务器,以便在日志
文件中出现异常时向你发出警报。但是,这种方法存在一些问题:

  • 日志文件以单行日志条目为导向,而异常由多行组成。
  • 没有机制来追踪日志文件中发生的异常的解决方案。你必须手动将异常复制粘贴到问题追踪器中。
  • 可能存在重复的异常,但没有自动机制将它们视为异常。

更好的方法是使用异常追踪服务。如图11-15所示,可将服务配置为通过类似 REST API向异常追踪服务报告异常。异常追踪服务可以对异常进行重复数据删除、生成警报并管理异常的解决方案。
在这里插入图片描述

11.3.6 使用审计日志模式

审计日志记录的目的是记录每个用户的操作。审计日志通常用于帮助客户支持、确保合规性并检测可疑行为。每个审核日志条目都记录用户的身份、他们执行的操作以及业务对象。应用程序通常将审计日志存储在数据库表中。
有几种不同的方法来实现审计日志记录:

向业务逻辑添加审计日志代码
第一个也是最直接的选择是在整个服务的业务逻辑中使用审计日志代码。例如,每种服务方法都可以创建审核日志条目并将其保存在数据库中。这种方法的缺点是它将审计日志代码和业务逻辑交织在一起,从而降低了可维护性。另一个缺点是它可能容易出错,因为它依赖于开发人员编写审计日志代码。

使用面向切面编程
第二种选择是使用AOP。你可以使用AOP框架来定义自动拦截每个服务的方法调用,并持久化审计日志条目。这是一种更可靠的方法,因为它可以自动记录每个服务方法调用。使用AOP的主要缺点是只能记录调用的方法名称和它的参数,因此确定正在执行的业务对象,并生成面向业务的审计日志条目可能具有挑战性。

使用事件溯源
第三个也是最后一个选择是使用事件溯源来实现你的业务逻辑。如第6章所述,事件溯源自动为创建和更新操作提供审计日志。你需要在每个事件中记录用户的身份。但是,使用事件溯源的一个限制是它不记录查询。如果你的服务必须为查询创建日志条目,那么你还必须考虑其他选择。

11.4 使用微服务基底开发服务

开发服务的一种更快捷的方法是在微服务基底上构建服务。如图11-16所示,微服务基底是处理这些问题的框架或一组框架。使用微服务基底时,你只需编写很少的代码来处理这=些问题。
在这里插入图片描述

11.4.1 使用微服务基底

微服务基底是一个框架或一组框架,可以处理许多问题,包括:

  • 外部化配置。
  • 健康检査。
  • 应用程序指标。
  • 服务发现。
  • 断路器。
  • 分布式追踪。

它可以显著减少你需要编写的代码量。你甚至可能不需要编写任何代码。相反,你可以配置微服务基底以满足你的要求。微服务基底使你能够专注于开发服务的业务逻辑。

11.4.2 从微服务基底到服务网格

微服务基底是解决各种共性问题的好方法,例如断路器。但使用微服务基底的一个障碍是,你需要确保你使用的编程语言有对应的微服务基底框架或类库。
避免此问题的新兴替代方案是在所谓的服务网格中实现服务之外的某些功能。服务网格是网络基础设施,它调和服务与其他服务和外部应用程序之间的通信。如
图11-17所示,进出服务的所有网络流量都通过服务网格。它实现了各种共性的需求:包括断路器、分布式追踪、服务发现、负载均衡和基于规则的流量路由。服务网格还可以通过在服务之间使用基于TLS的机制来保护进程间通信。因此,你不再需要在服务中解决这些特定问题。
在这里插入图片描述
服务网格概念是一个非常有前途的想法。它使开发人员不必处理各种共性问题。此外服务网格的智能流量路由使你可以将部署与发布分开。它使你能够将新版本的服务部署到生产中,但只将其发布给某些用户,例如内部测试用户。

第12章 部署微服务应用

图12-2显示了一个生产环境的抽象视图。生产环境使开发人员能够配置和管理他们的服务、使用部署流水线部署新版本的服务,以及用户访问这些服务实现的功能。
在这里插入图片描述
生产环境必须实现四个关键功能:

  • 服务管理接口:使开发人员能够创建、更新和配置服务。理想情况下,这个接口是个可供命令行和图形部署工具调用的 REST API
  • 运行时服务管理:确保始终运行着所需数量的服务实例。如果服务实例崩溃或由于某种原因无法处理请求,则生产环境必须重新启动它。如果运行服务的主机发生崩溃则必须在其他主机上重新启动这些服务实例。
  • 监控:让开发人员深入了解服务正在做什么,包括日志文件和各种应用指标。如果出现问题,必须提醒开发人员。
  • 请求路由:将用户的请求路由到服务。

12.1 部署模式:编程语言特定的发布包格式

如图12-3所示,部署流水线构建可执行的JAR文件或者WAR文件。然后,它调用生成环境的服务管理接口来部署新版本。
在这里插入图片描述

12.1.1 使用编程语言特定的发布包格式进行部署的好处

快速部署
这种模式的一个主要好处是部署服务实例的速度相对较快:将服务复制到主机并启动它。如果服务是用Java编写的,则复制JAR或WAR文件。对于其他语言,例如 Node.js或Ruby,可以复制源代码。在任何一种情况下,需要通过网络复制的字节数相对较小。
此外,启动服务耗时很短。如果服务运行于自己独占的进程,则启动它。否则,如果服务是在同一Web容器(例如Tomcat)进程中运行的多个实例之一,则可以将其动态部署到Web容器中,也可以重新启动Web容器。由于没有额外的开销,因此启动服务通常很快。

高效的资源利用
这种模式的另一个主要好处是它可以相对高效地使用资源。多个服务实例共享机器及其操作系统。如果多个服务实例在同一进程中运行,则效率更高。

12.1.2 使用编程语言特定的发布包格式进行部署的弊端

尽管极具吸引力,但把服务作为特定于编程语言的发布包进行部署的模式有几个显著的缺点:

  • 缺乏对技术栈的封装。
  • 无法约束服务实例消耗的资源
  • 在同一台计算机上运行多个服务实例时缺少隔离。
  • 很难自动判定放置服务实例的位置。

12.2 部署模式:将服务部署为虚拟机

将作为虚拟机镜像打包的服务部署到生产环境中。每个服务实例都是一个虚拟机。
在这里插入图片描述

12.2.1 将服务部署为虚拟机的好处

将服务作为虚拟机的模式具有许多优点:

  • 虚拟机镜像封装了技术栈:包含了服务及其所有依赖项
  • 隔离的服务实例:每个服务实例以完全隔离的方式运行
  • 使用成熟的云计算基础设施

12.2.2 将服务部署为虚拟机的弊端

将服务作为虚拟机的模式也有一些缺点:

  • 资源利用效率较低:每个服务实例拥有一整台虚拟机的开销,包括其操作系统
  • 部署速度相对较慢:通常需要几分钟,有很多内容需要通过网络传输
  • 系统管理的额外开销:必须承担给操作系统和运行时打补丁的责任

12.3 部署模式:将服务部署为容器

容器是一种更现代、更轻量级的部署机制,是一种操作系统级的虚拟化机制。如图12-7所示,容器通常包含一个或多个在沙箱中运行的进程,这个沙箱将它们与其他容器隔离。
在这里插入图片描述
创建容器时,可以指定它的CPU和内存资源,以及依赖于容器实现的ⅣO资源等。容器运行时强制执行这些限制,并防止容器占用其机器的资源。使用Docker编排框架(如Kubernetes)时,指定容器的资源尤为重要。这是因为编排框架使用容器请求的资源来选择运行容器的底层机器,从而确保机器不会过载。
图12-8显示了将服务部署为容器的过程。
在这里插入图片描述

12.3.1 使用Docker部署服务

略~

12.3.2 将服务部署为容器的好处

将服务部署为容器有几个好处。首先,容器具有虚拟机的许多好处:

  • 封装技术栈,可以用容器的API实现对服务的管理。
  • 服务实例是隔离的。
  • 服务实例的资源受到限制。

但与虚拟机不同,容器是一种轻量级技术。容器镜像通常可以很快构建。

12.3.3 将服务部署为容器的弊端

容器的一个显著弊端是,你需要承担大量的容器镜像管理工作。你必须负责给操作系统和运行时打补丁。此外,除非使用托管容器解决方案(如Google Container Engine或AWS ECS),否则你必须管理容器基础设施以及容器运行可能需要的虚拟机基础设施。

12.4 使用Kubernetes部署FTGO应用程序

略~

第13章 微服务架构的重构策略

13.1 重构到微服务需要考虑的问题

13.1.1 为什么要重构单体应用

如第1章所述,微服务架构具有许多好处。它具有更好的可维护性、可测试性和可部署性,因此可以加速开发。微服务架构还具备可扩展性,并且改善了故障隔离。技术栈演化也更容易。但把单体重构到微服务是一项重大任务。它将把资源从新功能的开发中分流出去。因此,只有在能够解决重大业务问题的情况下,业务部门才可能考虑支持采用微服务。

13.1.2 绞杀单体应用

将单体应用程序转换为微服务的过程是应用程序现代化的一种形式。应用程序现代化是将遗留应用程序转换为现代架构和技术栈的过程。
“一步到位”方式(Big Bang Rewrite)是指,你企图从零开始开发一个全新的基于微服务的应用程序(彻底替换遗留的单体应用)。虽然从头开始并抛弃老代码库听起来很有吸引力,但它的风险极高,很可能以失败告终。你将花费数月甚至数年来复制现有功能,然后才能实现业务今天就需要的功能!此外,无论如何,你都需要开发和维护老的应用程序,这会打乱重写的工作,并意味着你有一个不断变化的目标。更重要的是,你可能会浪费时间重新实现不再需要的功能。
如图13-1所示,你应该逐步重构你的单体应用程序,而不是推倒重来。逐步构建一个新的、被称为绞杀者应用程序的应用。绞杀者应用程序由与单体应用程序结合使用的微服务组成。随着时间的推移,单体应用程序实现的功能数量会缩小,直到完全消失或者变成另一个微服务。
在这里插入图片描述

尽早并且频繁地体现出价值
逐步重构微服务架构的一个重要好处是可以立即获得投资回报。这与“一步到位”式的重写非常不同,“一步到位”在它“到位”之前不会带来任何好处。当逐步重构单体时,你可以使用新的技术栈和现代、高速、 DevOps风格的开发与交付流程开发每项新服务。因此,你的团队的交付速度会随着时间的推移而稳步增加。
更重要的是,你可以先将应用程序的高价值部分迁移到微服务架构。
能够更早地交付价值的另一个好处是,它有助于获得业务团队对重构工作的支持。

尽可能少地对单体做出修改
在迁移到微服务架构时,应避免对单体进行大范围的修改。当然,在迁移到微服务的过程中,对单体进行修改是不可避免的。

部署基础设施:这怼你来说还为时过早
你可能很想通过选择技术和构建基础设施来开始迁移到微服务。
你甚至可能会感受到来自业务人员和身边PaaS供应商的压力,开始在这种基础设施上花钱。
虽然看起来预先建立这种基础设施非常诱人,但我建议只在架构重构的前期进行最小的投资。你唯一不可或缺的是执行自动化测试的部署流水线。我建议推迟任何涉及重大投资的技术基础设施决策,直到你获得开发微服务架构应用的实际经验。只有在运行一些服务后,你才会有经验选择技术基础设施。

13.2 将单体应用重构为微服务架构的若干策略

13.2.1 将新功能实现为服务

“挖坑法则”(The Law of Holes)指出:如果你发现自己已经陷人了困境,就不要再给自己继续挖坑了。当你的单体应用变得无法管理时,这是一个很好的可供参考的建议。换句话说,如果你有一个庞大的、复杂的单体应用程序,请不要通过向单体添加代码来实现新功能。这将使你的单体变得更庞大,更难以管理。相反,你应该将新功能实现为服务。
这是开始将单体应用程序迁移到微服务架构的好方法。它降低了单体的生长速度,加速了新功能的开发(因为是在全新的代码库中进行开发),还能快速展示采用微服务架构的价值。

把新的服务与单体集成
图13-2显示了将新功能实现为服务后的应用程序架构。除了新服务和单体之外,该架构还包括另外两个将服务集成到应用程序中的元素:

  • API Gateway:将对新功能的请求路由到新服务,并将遗留请求路由到单体
  • 集成胶水代码:将服务与单体结合。它使服务能够访问单体所拥有的数据,并能够调用单体实现的功能。
    在这里插入图片描述
    何时把心功能实现为服务
    理想情况下,你应该在绞杀者应用程序中而不是在单体中实现每个新功能。你将实现新功能作为新服务或作为现有服务的一部分。这样你就可以避免和单体代码库打交道。不幸的是,并非每个新功能都可以作为服务实现。
    因为微服务架构的本质是一组围绕业务功能组织的松耦合服务。例如,某个功能可能太小而无法成为有意义的服务。例如,你可能只需要向现有类添加一些字段和方法。或者新功能可能与单体中的代码紧耦合。如果你尝试将此类功能实现为服务,则通常会发现,由于过多的进程间通信而导致性能下降。你可能还会遇到数据一致性的问题。如果新功能无法作为服务实现,则解决方案通常是首先在单体中实现新功能。之后,你可以将该功能以及其他相关功能提取到自己的服务中。

13.2.2 隔离表现层与后端

缩小单体应用程序的一个策略是将表现层与业务逻辑和数据访问层分开。典型的企业应用程序包含以下各层:

  • 表现逻辑层:它由处理HTTP请求的模块组成,并生成实现Web UI的HTML页面。在具有复杂用户界面的应用程序中,表现层通常包含大量代码。
  • 业务逻辑层:由实现业务规则的模块组成,这些模块在企业应用程序中可能很复杂。
  • 数据访问逻辑层:包含访问基础设施服务(如数据库和消息代理)的模块。

表现逻辑层与业务和数据访问逻辑层之间通常存在清晰的边界。业务层具有粗粒度API,由一个或多个封装业务逻辑的门面(Facade)组成。这个API是一个自然的接缝,你可以沿着它将单体分成两个较小的应用程序,如图13-3所示。
一个应用程序包含表现层,另一个包含业务和数据访问逻辑层。分割后,表现逻辑应用程序对业务逻辑应用程序进行远程调用。
以这种方式拆分单体应用有两个主要好处。它使你能够彼此独立地开发、部署和扩展这两个应用程序。特别是,它允许表现层开发人员快速迭代用户界面并轻松执行A/B测试,而无须部署后端。这种方法的另一个好处是它公开了业务逻辑的一组远程API,可以被稍后开发的微服务调用。
在这里插入图片描述

13.2.3 提取业务能力到服务中

你想要提取到服务中的功能是对单体应用自上而下的一个“垂直切片”。
如图13-4所示,此代码从单体中提取并移至独立服务中。API Gateway将调用提取的业务功能的请求路由到该服务,并将其他请求路由到单体。
在这里插入图片描述
提取服务时会遇到以下这些挑战:

  • 拆解领域模型
  • 重构数据库

拆解领域模型
为了提取服务,你需要从单体的领域模型中提取服务相关的领域模型。你需要进行大动作来拆分领域模型。你将遇到的一个挑战是消除跨越服务边界的对象引用。
解决此问题的一个好方法是根据DDD聚合进行思考,如第5章所述。聚合使用主键而不是对象引用相互引用。

重构数据库
拆分领域模型不仅仅涉及更改代码。领域模型中的许多类都是在数据库中持久化保存的。它们的字段映射到具体的数据库模式。因此,当你从单体中提取服务时,你也会移动数据。你需要将表从单体的数据库移动到服务的数据库。
此外,拆分实体时,需要拆分相应的数据库表并将新表移动到服务中。

复制数据以避免更广泛的更改
延迟并可能避免进行这些昂贵更改的一种好方法是使用类似《数据库重构》一书中描述的方法。重构数据库的一个主要障碍是更改该数据库的所有客户端以使用新模式。本书中提出的解决方案是在过渡期内保留原模式,并使用触发器在原模式和新模式间同步。然后你可以将客户端从旧模式迁移到新模式。
从单体中提取服务时,我们可以使用类似的方法。例如,在提取Delivery实体时,我们将Order实体在过渡期内大部分保持不变。如图13-7所示,我们将与交付相关的字段设置为只读,并通过将数据从Delivery Service复制回单体来使其保持最新。
在这里插入图片描述

13.3 设计服务与单体的协作方式

略~

13.4 将新功能实现为服务:处理错误配送订单

略~

13.5 从单体重提取送餐管理功能

略~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值