这七个系列的文章现在完成:

  1. 微服务介绍
  2. 构建微服务:使用API​​网关
  3. 构建微服务:微服务架构中的进程间通信
  4. 微服务架构中的服务发现
  5. 微服务的事件驱动数据管理(本文)
  6. 选择微服务部署策略
  7. 将重组重构为微服务

您还可以下载完整的文章集,以及使用NGINX Plus实现微服务的信息,作为电子书 -微服务:从设计到部署

这是关于使用微服务构建应用程序的第五篇文章。在第一篇文章介绍了微服务架构模式,并讨论的好处和使用微服务的缺点。的第二第三系列中的文章描述了一个微服务架构内的通信的不同方面。在第四篇文章探讨了服务发现的密切相关的问题。在本文中,我们改变齿轮,并看看在微服务架构中出现的分布式数据管理问题。

微服务和分布式数据管理的问题

单片应用程序通常具有单个关系数据库。使用关系数据库的一个关键好处是您的应用程序可以使用ACID事务,这提供了一些重要的保证:

  • 原子性 - 原子性地进行改变
  • 一致性 - 数据库的状态始终一致
  • 隔离 - 即使事务被并发执行,它们似乎被顺序执行
  • 耐久性 - 一旦事务已提交,它不会撤消

因此,您的应用程序可以简单地开始事务,更改(插入,更新和删除)多个行,并提交事务。

使用关系数据库的另一个好处是它提供了SQL,它是一种丰富的,声明性的和标准化的查询语言。您可以轻松地编写组合来自多个表的数据的查询。然后,RDBMS查询计划器确定执行查询的最佳方法。您不必担心低级别的详细信息,例如如何访问数据库。并且,因为您的应用程序的所有数据都在一个数据库中,所以很容易查询。

不幸的是,当我们转向微服务架构时,数据访问变得更加复杂。这是因为每个微服务拥有的数据对于该微服务是私有的,并且只能通过其API来访问。封装数据确保微服务松耦合并且可以彼此独立地演进。如果多个服务访问相同的数据,则模式更新需要耗费时间,协调地更新所有服务。

更糟糕的是,不同的微服务通常使用不同种类的数据库。现代应用程序存储和处理各种各样的数据,关系数据库并不总是最好的选择。对于某些使用情况,特定的NoSQL数据库可能具有更方便的数据模型,并提供更好的性能和可扩展性。例如,存储和查询文本的服务使用文本搜索引擎(例如Elasticsearch)是有意义的。类似地,存储社交图数据的服务应当使用图形数据库,例如Neo4j。因此,基于微服务的应用程序通常使用SQL和NoSQL数据库的混合,所谓的多语言持久化方法。

用于数据存储的分区的多磁盘持久性架构具有许多优点,包括松耦合服务和更好的性能和可扩展性。然而,它确实引入了一些分布式数据管理挑战。

第一个挑战是如何实现在多个服务之间保持一致性的业务事务。要了解为什么这是一个问题,让我们看看一个在线B2B商店的例子。客户服务维护有关客户的信息,包括他们的信用额度。订单服务管理订单,并且必须验证新订单不超过客户的信用额度。在此应用程序的单片版本中,订单服务可以简单地使用ACID事务来检查可用信用额并创建订单。

相比之下,在微服务架构中,ORDER和CUSTOMER表对它们各自的服务是私有的,如下图所示。

订单服务无法直接访问CUSTOMER表。它只能使用由客户服务提供的API。订单服务可能使用分布式事务,也称为两阶段提交(2PC)。然而,2PC在现代应用中通常不是可行的选择。该CAP定理需要您的可用性和酸风格的一致性之间做出选择和可用性通常是更好的选择。此外,许多现代技术,如大多数NoSQL数据库,不支持2PC。维护服务和数据库之间的数据一致性至关重要,因此我们需要另一个解决方案

第二个挑战是如何实现从多个服务检索数据的查询。例如,让我们假设应用程序需要显示一个客户和他最近的订单。如果订单服务提供用于检索客户订单的API,那么您可以使用应用程序端加入来检索此数据。应用程序从客户服务中检索客户,并从订单服务检索客户的订单。然而,假设订单服务仅支持通过其主键查找订单(也许它使用仅支持基于主键的检索的NoSQL数据库)。在这种情况下,没有明显的方法来检索所需的数据。

事件驱动架构

对于许多应用程序,解决方案是使用事件驱动架构。在此体系结构中,微服务在发生显着事件时发布事件,例如更新业务实体时。其他微服务订阅这些事件。当微服务接收到事件时,它可以更新其自己的业务实体,这可能导致更多的事件被发布。

您可以使用事件来实现跨多个服务的业务事务。事务包括一系列步骤。每个步骤包括微服务更新业务实体和发布触发下一步骤的事件。以下图表序列显示了如何使用事件驱动方法在创建订单时检查可用信用额。微服务通过Message Broker交换事件。

  1. 订单服务创建状态为NEW的订单,并发布订单创建事件。

  2. 客户服务使用订单创建事件,为订单预留信用,并发布信用预留事件。

  3. 订单服务消耗预留信用事件,并将订单状态更改为OPEN。

更复杂的情况可能涉及额外的步骤,例如在检查客户信用的同时预留库存。

如果(a)每个服务原子性地更新数据库并发布事件(更多),(b)Message Broker保证事件至少传递一次,那么您可以实现跨多个服务的业务事务。重要的是要注意,这些不是ACID事务。它们提供了更弱的保证,例如最终的一致性。这个交易模型被称为BASE模型

您还可以使用事件来维护预先加入由多个微服务拥有的数据的物化视图。维护视图的服务订阅相关事件并更新视图。例如,维护“客户订单”视图的“客户订单视图更新程序”服务订阅客户服务和订单服务发布的事件。

当客户订单视图更新程序服务收到客户或订单事件时,它会更新客户订单视图数据存储。您可以使用文档数据库(如MongoDB)实现客户订单视图,并为每个客户存储一个文档。客户订单视图查询服务通过查询客户订单视图数据存储来处理客户和最近订单的请求。

事件驱动架构有几个好处和缺点。它实现跨多个服务的事务的实现,并提供最终的一致性。另一个好处是,它还使应用程序能够维护物化视图。一个缺点是编程模型比使用ACID事务时更复杂。通常,您必须实现补偿事务以从应用程序级故障中恢复;例如,如果信用检查失败,您必须取消订单。此外,应用程序必须处理不一致的数据。这是因为飞行中交易所做的更改是可见的。如果应用程序从尚未更新的实例化视图读取,那么应用程序也可以看到不一致。另一个缺点是订户必须检测和忽略重复的事件。

实现原子性

在事件驱动的架构中,还存在原子级更新数据库和发布事件的问题。例如,订单服务必须在ORDER表中插入一行,并发布订单创建事件。这两个操作必须原子性地完成。如果服务在更新数据库之后但在发布事件之前崩溃,则系统会不一致。确保原子性的标准方法是使用涉及数据库和Message Broker的分布式事务。然而,由于上述原因,如CAP定理,这正是我们不想做的。

使用本地事务发布事件

实现原子性的一种方式是应用程序使用仅涉及本地事务多步骤过程来发布事件。诀窍是在存储业务实体的状态的数据库中有一个EVENT表,该表用作消息队列。应用程序开始(本地)数据库事务,更新业务实体的状态,将事件插入EVENT表,并提交事务。单独的应用程序线程或进程查询EVENT表,将事件发布到Message Broker,然后使用本地事务将事件标记为已发布。下图显示了设计。

Order Service在ORDER表中插入一行,并将一个Order Created事件插入EVENT表中。事件发布器线程或进程查询EVENT表以查找未发布的事件,发布事件,然后更新EVENT表以将事件标记为已发布。

这种方法有几个好处和缺点。一个好处是,它保证为每个更新发布事件而不依赖于2PC。此外,应用程序发布业务级事件,这消除了推断它们的需要。这种方法的一个缺点是它潜在地容易出错,因为开发人员必须记住发布事件。这种方法的局限性在于,当使用一些NoSQL数据库时,由于它们的有限事务和查询能力,实施是有挑战性的。

此方法通过让应用程序使用本地事务来更新状态和发布事件,从而消除了对2PC的需要。让我们来看看通过让应用程序简单地更新状态来实现原子性的方法。

挖掘数据库事务日志

另一种实现原子性而不使用2PC的方法是通过挖掘数据库事务或提交日志的线程或进程发布事件。应用程序更新数据库,导致更改记录在数据库的事务日志中。事务日志Miner线程或进程读取事务日志并将事件发布到Message Broker。下图显示了设计。

这种方法的一个例子是开源的LinkedIn数据库项目。Databus挖掘Oracle事务日志并发布与更改相对应的事件。LinkedIn使用Databus来保持各种派生数据存储与记录系统一致。

另一个示例是AWS DynamoDB中的流机制,这是一个托管NoSQL数据库。DynamoDB流包含在过去24小时内对DynamoDB表中的项目进行的更改(创建,更新和删除操作)的按时间排序的顺序。应用程序可以从流中读取这些更改,例如,将其作为事件发布。

事务日志挖掘具有各种好处和缺点。一个好处是,它保证为每个更新发布事件,而不使用2PC。事务日志挖掘还可以通过将事件发布与应用程序的业务逻辑分离来简化应用程序。主要缺点是事务日志的格式是每个数据库专有的,甚至可能在数据库版本之间更改。此外,可能难以从记录在事务日志中的低级更新逆向工程化高级业务事件。

事务日志挖掘消除了对2PC的需要,让应用程序做一件事:更新数据库。现在让我们看一个不同的方法,消除更新,并完全依赖事件。

使用事件源

事件源通过使用完全不同的,以事件为中心的方法来持久化商业实体来实现没有2PC的原子性。不是存储实体的当前状态,而是存储状态改变事件的序列。应用程序通过重放事件来重建实体的当前状态。每当业务实体的状态改变时,新事件被附加到事件列表。由于保存事件是单个操作,它本质上是原子的。

要查看事件源如何工作,请考虑Order实体作为示例。在传统方法中,每个顺序映射到ORDER表中的一行以及例如ORDER_LINE_ITEM表中的行。但是当使用事件源时,订单服务以其状态改变事件的形式存储订单:已创建,已批准,已发货,已取消。每个事件包含足够的数据来重建Order的状态。

事件存储在事件存储中,事件存储是事件的数据库。商店具有用于添加和检索实体的事件的API。事件存储器的行为类似于我们之前描述的架构中的Message Broker。它提供了一个API,使服务能够订阅事件。事件存储将所有事件提供给所有感兴趣的订阅者。事件存储是事件驱动的微服务架构的主干。

事件源有几个好处。它解决了实现事件驱动架构中的一个关键问题,并且使得每当状态改变时可靠地发布事件成为可能。因此,它解决了微服务架构中的数据一致性问题。此外,因为它持久化事件而不是域对象,它大部分避免了对象关系阻抗失配问题。事件源还提供对业务实体所做的更改的100%可靠的审计日志,并且使得可以实现在任何时间点确定实体的状态的时态查询。事件源的另一个主要优点是,您的业务逻辑包括交换事件的松散耦合的业务实体。

事件源也有一些缺点。它是一种不同的和不熟悉的编程风格,所以有一个学习曲线。事件存储器仅直接支持通过主键查找业务实体。您必须使用命令查询责任分离(CQRS)来实现查询。因此,应用程序必须处理最终一致的数据。

概要

在微服务体系结构中,每个微服务都有自己的私有数据存储。不同的微服务可能使用不同的SQL和NoSQL数据库。虽然此数据库架构具有显着的优势,但它会产生一些分布式数据管理挑战。第一个挑战是如何实现在多个服务之间保持一致性的业务事务。第二个挑战是如何实现从多个服务检索数据的查询。

对于许多应用程序,解决方案是使用事件驱动架构。实现事件驱动架构的一个挑战是如何以原子方式更新状态以及如何发布事件。有几种方法可以实现这一点,包括使用数据库作为消息队列,事务日志挖掘和事件源。

在将来的博客文章中,我们将继续深入了解微服务的其他方面。

这七个系列的文章现在完成:

  1. 微服务介绍
  2. 构建微服务:使用API​​网关
  3. 构建微服务:微服务架构中的进程间通信
  4. 微服务架构中的服务发现
  5. 微服务的事件驱动数据管理(本文)
  6. 选择微服务部署策略
  7. 将重组重构为微服务

您还可以下载完整的文章集,以及使用NGINX Plus实现微服务的信息,作为电子书 -微服务:从设计到部署

results matching ""

    No results matching ""