本文选自 《微服务架构设计模式》 一书。
在微服务架构中编写查询非常具有挑战性。查询通常需要检索分散在多个服务所拥有的数据库中的数据。但是,你不能使用传统的分布式查询处理机制,因为即使技术上可行,它也会打破服务之间的隔离和封装。下面将介绍一种在微服务架构中实现查询操作的最简单方法——API组合模式。
这个模式通过调用拥有数据的服务并组合结果来实现查询操作。下图显示了该模式的结构。它有两种类型的参与者:
API组合器:它通过查询数据提供方的服务来实现查询操作。
数据提供方服务:拥有查询返回的部分数据的服务。
上图显示了三个提供方服务。API 组合器通过从提供方服务检索数据并组合结果来实现查询。API 组合器可能是需要数据呈现网页的客户端,例如 Web 应用程序。或者,它可能是一个服务,例如 API Gateway 及后端前置模式 ,这个模式将查询操作公开为 API 接口。
是否可以使用此模式实现特定查询操作取决于几个因素,包括数据的分区方式、拥有数据的服务公开的 API 的功能,以及服务使用数据库的功能,等等。例如,即使提供方服务拥有用于检索所需数据的 API,聚合器也可能需要执行大量数据集的低效内存连接。稍后,你将看到使用此模式无法实现查询操作的例子。但幸运的是,有许多场景可以应用这种模式。为了看到它的实际效果,我们来举个例子。
使用 API 组合模式实现 findOrder() 查询操作
findOrder() 查询操作相当于一个简单的基于主键的 equi-join 查询。可以假设每个提供方服务都有一个 API接口,用于通过 orderId 检索所需的数据。因此,采用 API 组合模式来实现 findOrder() 查询操作似乎是合情合理的。API 组合器调用四个服务并将结果组合在一起。下图显示了Find Order Composer的设计。
显而易见,API 组合模式非常简单。让我们看一下在应用此模式时必须解决的几个设计问题。
使用此模式时,你必须解决两个设计问题:
确定架构中的哪个组件是查询操作的 API 组合器。
如何编写有效的聚合逻辑。
让我们看看每个问题。
这是你必须做出的一个决定,选择由谁来扮演查询操作的 API 组合器这个角色。你有三个选择。第一个选择,如下图所示,是由服务的客户端扮演 API 组合器的角色。
实现 Order Status 视图并在同一局域网上运行的前端客户端(如 Web 应用程序)可以使用此模式有效地检索订单详细信息。对于防火墙之外的客户以及通过较慢网络访问的服务,此选择可能不实用。
第二个选择如下图所示,由实现应用程序外部 API 的 API Gateway 来扮演 API 组合器的角色,用来完成查询操作和查询结果的组合。
如果查询操作是应用程序外部 API 的一部分,则此选择有意义。API Gateway 不是将请求路由到另一个服务,而是实现 API 组合逻辑。这种方法使得在防火墙外运行的客户端(例如移动设备)能够通过单个 API 调用有效地从众多服务中检索数据。
第三个选择如下图所示,是将 API 组合器实现为独立的服务。
此选择可以用于由多个服务在内部使用的查询操作。此操作还可用于外部可访问的查询操作,由于它们的聚合逻辑过于复杂,因此无法在 API Gateway 中完成查询,必须使用单独的服务。
在开发分布式系统时,我们一直努力降低服务之间的延迟。API 组合器应尽可能地并行调用提供方服务,最大限度地缩短查询操作的响应时间。例如,Find Order Aggregator 应该同时调用这四个服务,因为调用之间没有依赖关系。但有时,API 组合器需要一个提供方服务的结果才能调用另一个服务。在这种情况下,它需要按顺序调用一部分(但希望不是全部)提供方服务。
高效执行顺序和并行服务调用混合的逻辑可能很复杂。为了使 API 组合器达到较高的可维护性、性能和可扩展性,它应该使用基于 Java CompletableFuture、RxJava 可观测或其他类似的响应式设计。我将在后面讲 API Gateway 模式的时候再深入讨论这个主题。
此模式是在微服务架构中实现查询操作的简单直观方式。但它也有一些缺点:
增加了额外的开销。
带来可用性降低的风险。
缺乏事务数据一致性。
我们来逐一分析。
增加了额外的开销
这种模式的一个缺点是它需要调用多个服务和查询多个数据库,这带来了额外的开销。在单体应用程序中,客户端可以使用单个请求检索数据,这通常会执行单个数据库查询。相比之下,使用 API 组合模式会涉及多个请求和多个数据库查询。因此,它需要更多计算和网络资源,运行应用程序的成本也相应增加。
带来可用性降低的风险
这种模式的另一个缺点是导致可用性降低。如第 3 章所述,操作的可用性随着所涉及的服务数量而下降。因为查询操作的实现涉及至少三个服务:API 组合器和至少两个提供方服务,其可用性将显著小于单个服务的可用性。例如,如果单个服务的可用性为 99.5%,则调用四个提供方服务的 findOrder() 接口的可用性为 99.5%(4+1)=97.5% !
你可以使用几种策略来提高可用性。一种策略是 API 组合器在提供方服务不可用时,返回先前缓存的数据。API 组合器有时会缓存提供方服务返回的数据,以提高性能。它还可以使用此缓存来提高可用性。如果提供方服务不可用,则 API 组合器可以从缓存中返回数据,尽管这些缓存数据可能是过时的。
另一种提高可用性的策略是让 API 组合器返回不完整的数据。例如,假设KitchenService 暂时不可用。findOrder() 查询操作的 API 组合器可以从响应中省略该服务的数据,因为用户界面仍然可以显示其他有用的信息。你将在后面一章中看到有关 API 设计、缓存和可靠性的更多详细信息。
缺乏事务数据一致性
API 组合模式的另一个缺点是缺乏数据一致性。单体应用程序通常使用一个数据库事务执行查询操作。ACID 事务受制于隔离级别的约束,可以确保应用程序具有一致的数据视图,即使它执行多个数据库查询。相反,API 组合模式则是针对多个数据库执行查询。这种方式 存在一种风险,即查询操作将返回不一致的数据。
例如,从 Order Service 检索的 Order 可能处于 CANCELED 状态,而从 Kitchen Service 检索的相应 Ticket 可能尚未取消。API 组合器必须解决这种差异,因为这会增加代码复杂性。更糟糕的是,API组合器可能无法总是检测到不一致的数据,并将其返回给客户端。尽管存在这些缺点,API 组合模式还是非常有用的。你可以使用它来实现许多查询操作。但是有一些查询操作无法使用此模式有效实现。
以上内容摘自《微服务架构设计模式》一书。
《微服务架构设计模式》 作者:[美]克里斯·理查森(Chris Richardson) 译者:喻勇
作者 Chris Richardson 是世界十大软件架构师之一、微服务架构先驱,这是一本微服务实用落地指南。本书涵盖 44 个架构设计模式,系统解决服务拆分、事务管理、查询和跨服务通信等难题。
易宝支付CTO陈斌、PolarisTech 联合创始人蔡书、才云科技CEO张鑫等多位专家鼎力推荐。
松哥也在做微服务,老实说,微服务技术上并没有太多难点,真正的难点在于如何进行微服务的架构设计,如何进行服务拆分,这是难点,从这个角度来说,这本书值得一看。
接下来就是送书啦,本月第三次无套路送书,4本《微服务架构设计模式》 由“机械工业出版社华章公司”赞助,在此表示感谢。
送书规则: 在下方留言区,用一句话证明你是程序员 。
松哥会挑选 4 位幸运读者,《微服务架构设计模式》包邮到家(平时分享转发、点赞较多的小伙伴获奖概率更大哦~
着急的小伙伴,可以点击阅读原文购买。
● 天天吹微服务,单体应用有啥不好?
● 手把手带你入门 Spring Security!
● MyBatis 中 @Param 注解的四种使用场景,最后一种经常被人忽略!
● 给数据库减负的八个思路
● Spring Boot 邮件发送的 5 种姿势!
● 为了帮助前后端分离的新手,我做了一次大胆的尝试!
● 公司倒闭 1 年了,而我当年的项目上了 GitHub 热榜
● Spring Boot 打包成的可执行 jar ,为什么不能被其他项目依赖?
● Java 中的 jar ,天天见,可是你知道它的运行机制吗?
你点的每个在看,我都认真当成了喜欢