1:如何定义微服务边界
定义微服务的边界,这可能是每个人会碰到的第一个挑战。每个微服务必须成为应用的一部分,并且每
个微服务应该是自治的,这是一种优势和并存的情况。那么要如何定义边界?
首先,需要关注应用的逻辑领域模型和相关数据。必须尝试识别同一个应用中解耦后的数据孤岛和不同
的上下文。每个上下文都可以有不同的商业语言(不同的业务术语),上下文的定义和管理应该独立进
行。在不同上下文中使用的术语和实体可能听起来相似,但有时在特定上下文中的一个业务概念在另一
个上下文中可能会被用于不同的目,甚至可能使用不同名称。例如,同一个“用户”,可以是身份或会
员系统上下文中的“用户”,是 CRM 中的“客户”,甚至还是订单上下文中的“买方”等。
识别多个应用上下文以及每个上下文所对应不同领域之间边界的方法,也能用来识别每个业务微服务和
相关领域模型和数据的边界。我们需要尽可能降低这些微服务之间的耦合
2:如何创建从多个微服务获取数据的查询
如何实现从多个微服务获取数据的查询,同时避免远程客户端和微服务之间不必要的
通信。例如一个移动 App 需要一个页面来展示由购物篮、产品目录和用户身份微服务包含的用户信
息。再如一个复杂的报表系统涉及到位于多个微服务的多个表。适合的解决方案取决于查询的复杂性。
但无论如何都需要一种方式来聚合信息,以提高系统的通信效率。最流行的解决方案如下。
API 网关:对于从多个拥有不同数据库的微服务进行的简单数据聚合,推荐的方式是通过名为 API 网关
的机制聚合微服务。然而使用这种模式时需要当心,它可能成为系统瓶颈,也可能违反微服务自治的原
则。为了降低这些可能性,可以使用多个细粒度的 API 网关,每个网关主要面向系统的一个垂直“切
片”即业务领域。
CQRS 查询/读取表:另一种聚合多个微服务数据的方案是物化视图模式(Materialized View
Pattern),这种方案会提前(在实际查询发生前准备好非规范的数据)生成包含多个微服务数据的只
读表,并且这种表会使用适合客户端应用需求的格式。
假设有一个移动 App 的界面,如果有一个数据库,就可以使用一个 SQL 查询获取界面所需的全部数
据,该查询会针对多个表执行复杂的联接。但如果使用了分布在不同微服务中的多个数据库,就不能查
询这些数据库并创建 SQL 联接。此时复杂的查询将变成巨大的挑战。为此可以使用 CQRS 方案:在不
同数据库中创建一个只用作查询的非规范表,这个表可以针对复杂查询所需的数据专门设计,把应用界
面所需的字段和查询表的字段一一对应。这样的查询表还可以用在报表中。
这种方式不仅解决了最初的问题(如何跨微服务查询和联接),与复杂的 SQL 联接语句相比还能进一
步提升性能,因为应用所需的数据已经在查询表里了。当然,使用命令查询职责分离(CQRS)的查询/
读取表需要额外的开发工作,并且需要面对数据最终一致性问题。然而在需要高性能和高扩展性的协作
场景(或者竞争场景,取决于视角)下,应该使用 CQRS 来处理多数据库。
中心数据库的“冷数据”:对于可能不需要实时数据的复杂报表和查询,此时一种通用方案是将“热数
据”(微服务里的交易数据)导出为“冷数据”存储到报表专用的大型数据库中。这里的中心数据库系
统可以是基于大数据的系统,如 Hadoop,也可以是数据仓库,如 Azure SQL 数据仓库,甚至可以是单
独的报表专用 SQL 数据库(如果容量不是问题的话)
需要注意的是,中心数据库应该只用于不需要实时数据的报表查询,作为事实数据源的原始更新和交易
数据必须在微服务中。为了同步数据,可采用事件驱动通信(下一节会详细介绍),或使用数据库基础
结构提供的导入/导出工具。如果使用事件驱动通信的方式,整合流程将与上文提到的使用 CQRS 查询
表获取数据的方式类似。
然而,如果应用在设计上需要不断从多个微服务里进行聚合数据并进行复杂查询,上述设计将变得非常
糟糕,毕竟微服务之间应该尽可能地保持相互独立(使用中心冷数据库的报表分析系统除外)。通常在
遇到此类问题后,我们也许会合并微服务。我们需要在每个微服务的自治式进化和部署,以及强依赖、
高内聚和数据聚合之间进行权衡。
3:如何在多个微服务之间实现一致性
如上文所述,每个微服务拥有的数据是私有的,只能通过微服务 API 来访问。因此会遇到这样一个挑
战:如何跨多个微服务保持一致性的同时实现端到端的业务逻辑。
为了分析这个问题,让我们看看示例应用 eShopOnContainers 中的一个例子。目录(Catalog)微服务
维护着所有产品信息,包括价格。购物篮(Basket)微服务管理着用户加入购物篮的产品临时数据,包
括添加到购物篮时的产品价格。当目录服务中一个产品的价格发生变化后,购物篮中相同产品的价格也
应该变化,另外,系统应该告诉用户说购物篮里的某个产品的价格变了。
假如这个应用有一个单体式版本,当产品表中的价格发生变化时,产品子系统能够简单地使用 ACID 事
务来更新购物篮表里的价格。
然而在微服务应用里,产品表和购物篮表被各自的微服务所占有。任何微服务不应该在自己的事务中包
含其他微服务的表或存储,即使是直接查询也是不可以的。
目录微服务不能直接更新购物篮表,因为购物篮表被购物篮微服务占有。要更新购物篮微服务,产品微
服务应该使用基于异步通信,如集成事件(消息和基于事件的通信)的最终一致性。
根据 CAP 理论,我们需要在可用性和强 ACID 一致性之间作出选择。大多数微服务场景要求高可用性
和高扩展性,而非强一致性。重要应用必须保持随时在线,开发人员可以使用弱一致性或最终一致性的
技术来做到强一致性。这也是大多数基于微服务的架构所采取的方法。
此外,ACID 风格或两步提交事务不仅违背微服务原则,大多数 NoSQL 数据库(如 Azure CosmosDB、
MongoDB 等)也不支持两步提交事务。然而,跨服务和数据库维护数据的一致性非常重要。这个挑战
也关系到当某些数据需要实现冗余时,如何跨微服务执行变更的问题,例如需要更新目录微服务和购物
篮微服务中的产品名称或描述时。
因此,总结来说,解决这个问题的方法之一是,在微服务之间使用事件驱动通信和发布订阅系统来实现
最终一致性。