挑战1:如何定义每个微服务的边界
定义微服务边界可能是任何人遇到的第一个挑战。每个微服务都必须是应用程序的一部分,每个微服务都应该是自主的,具有它所传递的所有好处和挑战。但是你如何确定这些界限呢?
首先,您需要关注应用程序的逻辑域模型和相关数据。尝试在同一个应用程序中识别分离的数据孤岛和不同的上下文。每个上下文可以有不同的业务语言(不同的业务术语)。上下文应该独立定义和管理。在这些不同上下文中使用的术语和实体听起来可能类似,但您可能会发现,在特定上下文中,一个业务概念与另一个业务概念用于不同的上下文中,甚至可能有不同的名称。例如,用户可以在身份或成员关系上下文中被称为用户,在CRM上下文中被称为客户,在订购上下文中被称为买方,等等。
为每个上下文标识具有不同域的多个应用程序上下文之间的边界的方式,正是如何标识每个业务微服务及其相关域模型和数据的边界。您总是试图最小化这些微服务之间的耦合。本指南稍后将在《为每个微服务标识域模型边界》一节中详细介绍此标识和域模型设计。
挑战2:如何创建从多个微服务检索数据的查询
第二个挑战是如何实现从多个微服务检索数据的查询,同时避免从远程客户端应用程序与微服务进行聊天。例如,移动应用程序中的一个屏幕需要显示篮子、目录和用户标识微服务所拥有的用户信息。另一个例子是一个复杂的报表,其中包含位于多个微服务中的多个表。正确的解决方案取决于查询的复杂性。但无论如何,如果您想提高系统通信的效率,就需要一种聚合信息的方法。最流行的解决方案如下。
API网关。
对于来自拥有不同数据库的多个微服务的简单数据聚合,建议使用称为API网关的聚合微服务。但是,在实现这个模式时需要小心,因为它可能是系统中的一个瓶颈,并且可能违反微服务自治的原则。为了减少这种可能性,您可以拥有多个细粒度API网关,每个网关侧重于系统的垂直“部分”或业务区域。API网关模式将在后面的API网关部分中进行更详细的解释。
带有查询/读取表的CQRS(命令和查询责任隔离)
从多个微服务聚合数据的另一个解决方案是物化视图模式。在这种方法中,您可以预先(在实际查询发生之前准备非规范化的数据)生成一个只读表,其中包含多个微服务所拥有的数据。该表的格式适合客户端应用程序的需要。
考虑一下类似移动应用程序的屏幕。如果只有一个数据库,则可以使用SQL查询将该屏幕的数据拉到一起,该查询执行涉及多个表的复杂连接。但是,如果您有多个数据库,并且每个数据库都属于不同的微服务,则无法查询这些数据库并创建SQL联接。复杂的查询成为一个挑战。您可以使用CQRS方法来解决需求,您可以在一个仅用于查询的不同数据库中创建一个非规范化的表。该表可以专门为复杂查询所需的数据设计,应用程序屏幕所需的字段与查询表中的列之间具有一对一的关系。它也可用于报告目的。
这种方法不仅解决了最初的问题(如何跨微服务查询和连接),而且与复杂的连接相比,它还大大提高了性能,因为您已经在查询表中拥有应用程序所需的数据。当然,对查询/读取表使用命令和查询责任分离(CQRS)意味着需要额外的开发工作,您需要接受最终的一致性。尽管如此,协作场景(或竞争场景,取决于视角)中的性能和高可伸缩性要求是您应该对多个数据库应用cqr的地方。
中央数据库中的“冷数据”。
对于可能不需要实时数据的复杂报表和查询,一种常见的方法是将“热数据”(来自微服务的事务数据)作为“冷数据”导出到仅用于报表的大型数据库中。中央数据库系统可以是一个基于大数据的系统,比如Hadoop,一个基于Azure SQL数据仓库的数据仓库,甚至是一个只用于报表的SQL数据库(如果大小不是问题的话)。
请记住,这个集中式数据库只用于不需要实时数据的查询和报告。原始的更新和事务,作为您的真相来源,必须在您的微服务数据。同步数据的方式可以是使用事件驱动的通信(下一节将介绍)或使用其他数据库基础结构导入/导出工具。如果使用事件驱动的通信,那么集成过程将类似于前面为CQRS查询表所描述的传播数据的方式。
但是,如果您的应用程序设计涉及不断聚合来自多个微服务的信息以进行复杂的查询,则这可能是设计错误的征兆-微服务应尽可能与其他微服务隔离。(这不包括总是应该使用冷数据中心数据库的报表/分析)经常出现此问题可能是合并微服务的一个原因。您需要在每个微服务的演进和部署的自主性与强依赖性、内聚性和数据聚合之间取得平衡。
挑战3:如何跨多个微服务实现一致性
如前所述,每个微服务拥有的数据是该微服务的私有数据,只能使用其微服务API访问。因此,一个挑战是如何实现端到端业务流程,同时保持多个微服务之间的一致性。
为了分析这个问题,让我们看一个来自eShopOnContainers参考应用程序的示例。目录微服务维护所有产品的信息,包括产品价格。Basket microservice管理用户添加到购物篮中的产品项目的时间数据,其中包括添加到购物篮时的项目价格。当产品的价格在目录中更新时,该价格也应该在保存同一产品的活动篮中更新,另外,系统可能应该警告用户,说某个特定项目的价格自从添加到其篮中后已经发生了变化。
在这个应用程序的假设整体版本中,当products表中的价格发生变化时,catalog子系统可以简单地使用ACID事务来更新篮子表中的当前价格。
但是,在基于微服务的应用程序中,产品表和篮子表由各自的微服务拥有。任何微服务都不应该在自己的事务中包括另一个微服务拥有的表/存储,甚至在直接查询中也不应该包括,如图4-9所示。
微服务禁止直接访问另一个微服务中的表
Catalog microservice不应该直接更新Basket表,因为Basket表属于Basket microservice。为了更新Basket microservice,Catalog microservice应该使用可能基于异步通信(如集成事件(基于消息和事件的通信))的最终一致性。这就是eShopOnContainers引用应用程序如何跨微服务执行这种类型的一致性。
如CAP定理所述,您需要在可用性和ACID 强一致性之间进行选择。大多数基于微服务的场景要求可用性和高可伸缩性,而不是强一致性。任务关键型应用程序必须保持正常运行,开发人员可以通过使用处理弱一致性或最终一致性的技术来解决强一致性问题。这是大多数基于微服务的架构所采用的方法。
此外,ACID风格或两阶段提交事务不仅违反微服务原则;大多数NoSQL数据库(如Azure Cosmos DB、MongoDB等)不支持两阶段提交事务,这在分布式数据库场景中是典型的。然而,维护跨服务和数据库的数据一致性是必不可少的。这个挑战还涉及到当某些数据需要冗余时(例如,当您需要在目录微服务和篮子微服务中包含产品名称或描述时),如何在多个微服务中传播更改的问题。
解决这个问题的一个很好的方法是使用事件驱动通信和发布和订阅系统所连接的微服务之间的最终一致性。本指南后面的异步事件驱动通信一节将介绍这些主题。
挑战4:如何设计跨越微服务边界的通信
跨越微服务边界进行通信是一个真正的挑战。在这种情况下,通信并不是指应该使用什么协议(HTTP和REST、AMQP、消息传递等等)。相反,它解决了您应该使用什么样的通信方式,特别是您的微服务应该如何耦合。根据耦合的级别,当发生故障时,该故障对系统的影响将有很大的不同。
在像基于微服务的应用程序这样的分布式系统中,有这么多工件在移动,并且有跨多个服务器或主机的分布式服务,组件最终将失败。部分故障甚至更大的中断都会发生,因此您需要考虑到这种分布式系统中的常见风险,设计您的微服务和它们之间的通信。
一种流行的方法是实现基于HTTP(REST)的微服务,因为它们很简单。基于HTTP的方法是完全可以接受的;这里的问题与您如何使用它有关。如果使用HTTP请求和响应只是为了与来自客户端应用程序或API网关的微服务交互,那就没问题了。但是,如果您跨微服务创建长链的同步HTTP调用,并跨其边界进行通信,就好像微服务是单片应用程序中的对象一样,那么您的应用程序最终将遇到问题。
例如,假设您的客户机应用程序对单个微服务进行HTTP API调用,就像对微服务进行排序一样。如果顺序微服务在同一请求/响应周期内使用HTTP调用其他微服务,则创建一个HTTP调用链。一开始听起来可能很合理。然而,在沿着这条路走下去时,需要考虑的要点有:
- 阻塞和低性能。由于HTTP的同步特性,原始请求在所有内部HTTP调用完成之前不会得到响应。想象一下,如果这些调用的数量显著增加,同时对微服务的一个中间HTTP调用被阻塞。结果是性能受到影响,并且随着额外的HTTP请求的增加,总体可伸缩性将受到指数级的影响。
- 将微服务与HTTP耦合。业务微服务不应与其他业务微服务耦合。理想情况下,他们不应该“知道”其他微服务的存在。如果应用程序依赖于耦合微服务(如示例所示),则几乎不可能实现每个微服务的自治。
- 任何一个微服务出现故障。如果实现了一个由HTTP调用链接的微服务链,那么当任何一个微服务失败(最终它们将失败)时,整个微服务链都将失败。应设计一个基于微服务的系统,以便在部分故障期间尽可能继续工作。即使实现了使用指数退避或断路器机制重试的客户端逻辑,HTTP调用链越复杂,实现基于HTTP的故障策略就越复杂。
事实上,如果您的内部微服务是通过创建所述的HTTP请求链进行通信的,那么可以认为您有一个单片应用程序,但它是基于进程间HTTP而不是进程内通信机制的应用程序。
因此,为了强制微服务自治并具有更好的弹性,您应该尽量减少跨微服务的请求/响应通信链的使用。建议您只使用异步交互进行微服务间通信,可以使用异步消息和基于事件的通信,也可以使用(异步)HTTP轮询,独立于原始HTTP请求/响应周期。
异步通信的使用将在本指南后面的“异步微服务集成实施微服务的自治和基于异步消息的通信”一节中详细说明。
额外资源
- CAP theorem
https://en.wikipedia.org/wiki/CAP_theorem - Eventual consistency
https://en.wikipedia.org/wiki/Eventual_consistency - Data Consistency Primer
https://docs.microsoft.com/previous-versions/msp-n-p/dn589800(v=pandp.10) - Martin Fowler. CQRS (Command and Query Responsibility Segregation)
https://martinfowler.com/bliki/CQRS.html - Materialized View
https://docs.microsoft.com/azure/architecture/patterns/materialized-view - Charles Row. ACID vs. BASE: The Shifting pH of Database Transaction Processing
https://www.dataversity.net/acid-vs-base-the-shifting-ph-of-database-transaction-processing/ - Compensating Transaction
https://docs.microsoft.com/azure/architecture/patterns/compensating-transaction - Udi Dahan. Service Oriented Composition
http://udidahan.com/2014/07/30/service-oriented-composition-with-video/