第六章:微服务架构
自2012年以来,软件架构界最重要的变革可能是微服务的引入。这种开创性的架构风格如同2006年面向服务架构一样席卷全球。多年来,我们对这种革命性(和进化性)的架构风格有了深入了解,并且知道它如何解决我们在开发软件解决方案时所面临的众多复杂问题。随着时间推移,出现了许多新工具、技术、框架和平台使得微服务更易于设计、实施和管理。尽管如此,微服务可能是其中最为复杂的架构风格之一,需要正确使用才能充分发挥其作用。
基本拓扑结构
微服务架构风格是一个由具有单一目标且独立部署的服务组成的生态系统,通常通过API网关进行访问。来自用户界面(通常是微前端)或外部请求发起的客户端请求会调用API网关中明确定义的终点,并将用户请求转发给相应独立部署的服务。每个服务按顺序访问其自身数据,或向其他服务发送请求以获取非本身所拥有的数据。微服务架构风格基本拓扑结构如图6-1所示。
Figure 6-1. The basic topology of the microservices architecture style
请注意,尽管图6-1中每个服务都与一个单独的数据库相关联,但这并非必须(通常也不是)。相反,每个服务拥有自己的表集合,并以模式形式存储在高可用性数据库或专门用于特定领域的单个数据库中。需要理解的关键概念是只有拥有表格的服务才能访问和更新该数据。如果其他服务需要访问该数据,则必须向拥有微服务请求信息,而不能直接访问表格。在"界限上下文"部分详细描述了这种数据所有权方法背后推理。
在微服务中,API网关的主要职责是隐藏与其对应的服务端点位置和实现细节。此外,API网关还可以执行基础设施相关的横切功能,如安全性、指标收集和请求ID生成等。需要注意的是,在微服务架构中,与企业服务总线(ESB)不同,API网关不包含任何业务逻辑或编排调解操作。这一点非常重要,以保持有界上下文。
什么是微服务
微服务被定义为一个具有单一目标、独立部署的软件单元,其专注于执行特定任务。实际上,"微服务"这个术语并非指代服务的物理大小(如类的数量),而是指其功能。由于微服务旨在代表单一目标函数,因此通常具有细粒度。然而,并非总是如此。假设开发人员创建了一个包含312个类文件的服务,你是否仍然认为该服务可以被视为微服务?在这个例子中,该服务实际上只完成了一个任务------向客户发送电子邮件,并且每封可能发送给客户的300多封不同邮件都表示为一个独立的类文件,因此存在大量类文件。然而,由于它能够很好地完成一项任务(即向客户发送电子邮件),所以实际上可以将其视为一个微服务。这个例子说明了重点并不在于服务的大小,而在于它所提供的功能。
鉴于微服 务倾向于成为单一目标函数,在任何给定生态系统或应用程序环境中拥有数百甚至数千个独立部署的微服 务并不少见。大量分离出来、各种各样 的 服 务 是使 微 服 务 如 此 独 特 的 原 因 。 微 服 务 可 以 部 署 作为容器化服 务 ( 如 Docker) 或者作为无服务器函数。
界限上下文
正如前文所述,每个服务通常都拥有自己的数据,这意味着属于特定服务的表只能由该服务访问。举例来说,愿望清单服务可能会拥有其相应的愿望清单表。如果其他服务需要访问愿望清单数据,则必须通过向愿望清单服务请求信息来获取,而不能直接访问愿望清单表。这一概念被称为有界上下文,在埃里克·埃文斯在他的书《领域驱动设计》(Addison-Wesley)中提出了此概念。在微服务范围内,这意味着将表示某个领域或子领域(例如客户心愿清单)的所有源代码以及相应的数据结构和数据封装为一个独立单位,如图6-2所示。
Figure 6-2. A bounded context includes the source code and corresponding data for a given domain or subdomain
这个概念对于微服务而言至关重要。实际上,如果没有有界上下文的概念,微服务作为一种架构风格将无法存在。为了阐明这一点,想象一下250个微服务都在访问同一个单体数据库中的相同表集合。假设您进行了结构性变更(例如删除列或表),其中120个服务也需要进行访问。这样的变更需要同时修改、测试和部署120个独立的服务以及数据库变更,显然是不可行的情况。
在微服务中,有界上下文不仅促进了架构敏捷性(即能够迅速响应变化),还管理着微服务生态系统内的变更控制。有界上下文使得只有拥有数据的那个服务需要在结构数据发生改变时进行修改。如图6-3所示,在另一个有界上下文内需要访问数据的其他服务必须通过一个单独的契约来请求数据。该契约通常与物理数据库结构表示形式不同,因此通常无需对其他服务或契约进行修改。
Figure 6-3. The bounded context usually isolates changes to just the service owning the data
独有特征
微服务架构与所有其他架构风格迥然不同。使得微服务架构风格如此独特的三个因素是分布式数据、运营自动化和组织变革。
微服务是唯一需要将数据分解并分布在不同服务中的架构风格。这种需求的原因在于典型的微服务体系结构中通常存在大量的服务数量。如果没有将每个服务及其对应数据严格限定在一个有界上下文内,那么对底层应用程序数据进行结构性更改就根本行不通了。由于其他架构风格无法像微服务那样指定精细级别、单一目标性质,所以这些其他架构风格通常可以通过使用单块数据库来完成任务。
尽管将服务与其对应的数据在有界上下文中关联起来是微服务的主要目标之一,但在实际的商业应用程序世界中,很少能完全实现这一点。虽然大多数服务可能拥有自己的数据,在许多情况下,需要在两个或更多服务之间共享数据。共享少量(两到六个)服务之间数据的使用案例包括表耦合、外键约束、表之间触发器和物化视图,以及用于优化数据访问性能和共享所有权等。当数据在服务之间共享时,有界上下文被扩展为包括所有共享表以及访问该数据的所有服务。
运营自动化是将微服务与其他架构风格区分开来的另一个独特特点,这主要是由于典型生态系统中微服务数量庞大。人类不可能管理数百到数千个单独部署的软件单元的并行测试、部署和监控。因此,通常需要容器化以及像Kubernetes这样的服务编排和管理平台。这也导致了对微服务进行DevOps(而不仅仅是"好有"的需求)。由于服务数量众多,将服务交给独立的测试团队和发布工程师是不可行的。相反,团队拥有自己的服务,并负责相应的测试和发布工作。
这就引出了区分微服务与所有其他架构风格之间第三个事物------组织变革。微服务是唯一需要将开发团队组织成跨职能领域专业团队(包括用户界面、后端和数据库开发人员)的架构风格。这进一步要求在特定领域内确定服务所有者(通常为架构师)。测试人员、发布工程师以及数据库管理员(DBAs)通常也会与特定领域保持一致,使他们成为同一个虚拟团队中开发人员的一部分。通过这种方式,"虚拟团队"可以对其自己负责范畴内的服务进行测试和发布。
示例和用例
适合微服务架构风格的应用程序包括那些由业务工作流程中独立且不同的功能组成。一个经典的例子是标准的零售订单录入系统,其中包含下单、支付、通知客户、库存管理、订单处理、订单发货、订单跟踪以及发送调查问卷和数据分析等明确而独立的功能模块,这些模块可以作为单独部署的微服务来运行。
另一个有趣的微服务应用案例是商业智能和分析报告。每个报告、查询、数据源或数据分析都可以作为独立的微服务进行开发,所有这些微服务都可以访问存储在数据湖或数据仓库中的数据。尽管在报告使用案例中没有明确定义上下文与数据之间的关系,但这仍然适用于微服务,因为底层模式结构很少遭遇破坏性变化。相反地,旧模式被弃用并创建新模式来替代它们,从而帮助解决通常在处理事务性数据时遇到的变更控制问题。
斟酌与分析
尽管微服务备受欢迎且功能强大,但它也可能是最具挑战性的架构风格之一。在使用微服务时,团队通常首先面临的难题是确定服务粒度(即服务的大小)。单一职责原则很不幸地具有高度主观性,这使得就服务粒度达成共识变得困难。例如,发送电子邮件和短信通知的通知服务是否算作一个单一目标?或者通过电子邮件向客户发送通知是否算作一个单一目标?其他更客观地证明了选择合理性的因素包括代码易变性、容错能力、可扩展性和吞吐量以及访问控制等。
微服务架构风格中较为复杂的部分之一是如何进行各个服务之间的通信。它们应该采用异步还是同步通信方式?在工作流程中是否应该使用编排来调节各个服务之间的关系,并使用编排式调解器充当中介者,或者直接采用协同方式相互交流?每种通信选择都需要权衡考虑,这增加了回答这些问题的难度。
数据也是微服务中另一个棘手问题。如果愿望清单需要从产品目录获取产品信息,则应该通过REST进行跨服務请求数据呢?还是将所需数据缓存到内存数据库并扩展愿望清单表模式以包含必要的产品数据呢?抑或只需共享产品目录数据即可?同样地,这些选择都需要仔细权衡考虑,使得选择最合适选项变得非常困难。
关于微服务,还有许多更具挑战性的方面需要考虑,包括分布式事务管理、合同、代码重用技术和迁移模式等。幸运的是,在Neal Ford等人的著作《软件架构:困难之处》(O'Reilly)中详细介绍了所有这些复杂问题以及相应的权衡。
什么时候考虑这种风格
在选择微服务时,首要考虑之一是仔细审视应用功能。是否可以将应用功能分解为数十个或数百个相互独立且不同的功能单元?如果是这样,那么这就是一个值得考虑的良好架构,因为这正是微服务所体现的形态。
需要具备高度灵活性(快速响应变化能力)的应用非常适合采用微服务架构风格。从可维护性角度来看,有界上下文确保子域功能及其对应数据被紧密关联在一起,使得定位和编码更加便捷。测试也更加简洁明了,因为通常只需针对单一目标服务进行范围限定测试,并且因此实现完整回归测试更容易。部署风险显著降低,因为通常只需部署一个单一服务。大多数情况下可以通过热部署在工作日进行而无需等到周末进行大规模部署。
如果您具有高容错性和可扩展性需求,则也可以考虑采用微服务架构风格。在微服务中,可扩展性和容错性都体现在函数级别上,并且由于启动时间(MTTS)和恢复时间(MTTR)非常低廉(通常以毫秒计),所以微服务也非常适合弹性系统。
如果您计划在现有架构中实施大规模可扩展性,那么您也应该考虑采用微服务。在微服务中添加功能有时只需简单地创建一个服务,并将其封装在容器中,再创建一个API端点并部署该服务。我喜欢将这种技术称为"插入式"功能。换言之,如果您需要向系统添加额外的功能或特性,只需创建一个服务并将其纳入生态系统即可。听起来很容易对吧?理论上确实如此,但不使用微服务也存在许多原因,在下一节中会详细说明。
什么时候不要考虑这种风格
尽管微服务具有许多优点和强大功能,但确实存在一些原因可以避免使用这种架构风格,并考虑其他选择。首先要考虑的是工作流程的性质。微服务指的是单一目标、分别部署的软件组成应用程序。然而,如果发现所有这些独立部署的功能需要通过复杂的工作流程和大量的服务间通信或编排来绑定在一起,那么不应该考虑这种架构。
也许,不考虑微服务的最大的因素之一是与数据相关。如果数据紧密耦合且庞大(意味着将数据拆分为几十个到几百个独立模式或数据库是不可行的),则应远离微服务。所谓紧密耦合是指数据与功能之间相互关联得如此紧密,以至于尽管可以将一个应用程序的功能拆分为多个部署单元,但这些独立部署单元都需要访问相同的数据。此外,数据还可能通过外键约束、触发器、视图甚至存储过程等形式高度耦合(在真实世界中仍然存在)。如果数据过于紧密结合,请考虑采用基于服务架构而非微服务。
尽管某些文章和博客声称否认,但事实上微服务可能是当今最复杂 的架构风格之一,并因此显得非常昂贵。随着典型微服 务生态系统中 众多 服 务数量 的增加 ,平台、产品、框 架 和 数据库 的 许可费用 都会 增长 成 指数级 。 因此 , 如果您有 紧张 的 成本 和 时间 约束 ,请避免 这 种 架构 风格 并选择 类似 基于 服 务 构建 的 混 合 解决方案 。
有趣的是,大多数微服务架构并不适合高性能或高响应系统。尽管这一点可能令人惊讶,但实际上,微服务确实倾向于通过相互通信来访问数据和执行其他业务功能。由于服务之间采用远程通信方式,会出现三种类型的延迟:网络延迟、安全延迟和数据延迟。
网络延迟指信息包到达目标服务所需的时间,在使用不同的远程访问协议和服务之间物理距离时,该时间可以从30毫秒到300毫秒甚至更长。
安全延迟是验证或授权请求到达远程端点所需的时间,在设置了不同安全级别和访问控制水平的情况下,此延迟可以从几毫秒到300毫秒或更长。
数据延时对微服务性能影响最为显著,它指代其他服务代表您查询您没有拥有权利的数据所需花费的时间。例如,假设愿望清单服务需要访问产品描述,并与产品目录服务进行通信以请求数据,则在产品目录接收到请求后还必须进行额外数据库调用来检索产品描述信息;而在共享数据时只需要一个内部或外部连接调用即可在单体数据库中访问多种类型的数据。
架构特征
图6-4中的图表概括了微服务架构在体系结构特性方面的整体能力评估(星级评分)。一颗星表示该体系结构特性得到的支持较为有限,而五颗星则表示它非常适用于该特定的体系结构特性。
Figure 6-4. Architecture characteristics star ratings for microservices architecture