0105-微服务的事件驱动的数据管理
微服务和分布式数据管理问题
单片应用程序通常具有单个关系数据库。使用关系数据库的一个关键好处是您的应用程序可以使用ACID事务,这提供了一些重要的保证:
- 原子性 - 变化是自动进行的
- 一致性 - 数据库的状态始终保持一致
- 隔离 - 即使交易是同时执行的,看起来它们是连续执行的
- 耐久性 - 交易完成后,它不会被撤消
因此,您的应用程序可以简单地开始一个事务,更改(插入,更新和删除)多行,并提交事务。
使用关系数据库的另一个好处是它提供了SQL,它是一种丰富的,声明式的,标准化的查询语言。您可以轻松编写一个结合多个表中数据的查询。RDBMS查询规划器然后确定执行查询的最佳方式。您不必担心底层细节,例如如何访问数据库。而且,因为所有应用程序的数据都在一个数据库中,所以查询起来很容易。
不幸的是,当我们转向微服务架构时,数据访问变得更加复杂。这是因为每个微服务拥有的数据都是该微服务的私有数据,并且只能通过其API访问。封装数据可确保微服务松散耦合,并且可以相互独立进化。如果多个服务访问相同的数据,则模式更新需要耗时且协调地更新所有服务。
更糟糕的是,不同的微服务通常使用不同类型的数据库。现代应用程序存储和处理各种数据和关系数据库并不总是最好的选择。对于某些用例,特定的NoSQL数据库可能具有更方便的数据模型,并提供更好的性能和可伸缩性。例如,存储和查询文本以使用文本搜索引擎(如Elasticsearch)的服务是有意义的。同样,存储社交图数据的服务可能应该使用图形数据库,例如Neo4j。因此,基于微服务的应用程序通常使用SQL和NoSQL数据库的混合体,即所谓的多边形持久性方法。
用于数据存储的分区多边形持久架构具有许多优点,包括松散耦合的服务以及更好的性能和可伸缩性。但是,它确实引入了一些分布式数据管理挑战。
第一个挑战是如何实现在多个服务之间保持一致性的业务事务。要明白为什么这是一个问题,我们来看看一个在线B2B商店的例子。客户服务部门保存有关客户的信息,包括他们的信用额度。订单服务管理订单,并且必须验证新订单不超过客户的信用额度。在此应用程序的单一版本中,订单服务可以简单地使用ACID交易来检查可用信用并创建订单。
相反,在微服务架构中,ORDER和CUSTOMER表对于它们各自的服务是私有的,如下图所示。
订单服务无法直接访问CUSTOMER表。它只能使用客户服务提供的API。订单服务可能使用分布式事务,也称为两阶段提交(2PC)。但是,2PC在现代应用中通常不是可行的选择。该CAP定理需要你的可用性和ACID风格的一致性之间做出选择,和可用性通常是更好的选择。而且,许多现代技术(如大多数NoSQL数据库)不支持2PC。在服务和数据库之间保持数据一致性至关重要,所以我们需要另一种解决方案。
第二个挑战是如何实现从多个服务中检索数据的查询。例如,让我们想象应用程序需要显示一个客户和他最近的订单。如果订单服务提供用于检索客户订单的API,那么您可以使用应用程序端连接检索这些数据。应用程序从客户服务中检索客户,并从订单服务中检索客户的订单。但是,假设订单服务仅支持通过主键查找订单(也许它使用仅支持基于主键的检索的NoSQL数据库)。在这种情况下,没有明显的方法来检索所需的数据。
事件驱动架构
对于许多应用程序,解决方案是使用事件驱动的体系结构。在这种体系结构中,微服务在发生显着事件时发布事件,例如更新业务实体时。其他微服务订阅这些事件。当微服务接收到事件时,它可以更新自己的业务实体,这可能会导致发布更多事件。
您可以使用事件来实现跨多个服务的业务事务。交易由一系列步骤组成。每个步骤都包含一个微服务,用于更新业务实体并发布触发下一步的事件。以下图表序列显示如何在创建订单时使用事件驱动方法来检查可用信用。微服务通过Message Broker交换事件。
-
订单服务创建一个状态为NEW的订单并发布订单创建事件。
-
客户服务使用创建的订单事件,为订单保留信用,并发布信用保留事件。
-
订单服务使用信用保留事件,并将订单状态更改为OPEN。
更复杂的情况可能涉及其他步骤,例如在检查客户信用的同时预订库存。
(a)每个服务自动更新数据库并发布一个事件 - 稍后再介绍 - 和(b)Message Broker保证事件至少被传递一次,然后您可以实现跨越多个服务的业务事务。重要的是要注意这些不是ACID事务。它们提供了诸如最终一致性等更弱的保证。这种交易模式被称为BASE模型。
您还可以使用事件来维护预加入多个微服务拥有的数据的物化视图。维护视图的服务订阅相关事件并更新视图。例如,维护客户订单视图的客户订单视图更新程序服务订阅由客户服务和订单服务发布的事件。
当客户订单视图更新器服务收到客户或订单事件时,它会更新客户订单视图数据存储。您可以使用文档数据库(如MongoDB)来实现客户订单视图,并为每个客户存储一个文档。客户订单视图查询服务通过查询客户订单视图数据库来处理客户和近期订单的请求。
事件驱动的体系结构有几个优点和缺点。它可以实现跨越多个服务的交易并提供最终的一致性。另一个好处是它还使应用程序能够维护物化视图。一个缺点是编程模型比使用ACID事务更复杂。通常您必须实施补偿性事务才能从应用程序级别的故障中恢复; 例如,如果信用检查失败,您必须取消订单。此外,应用程序还必须处理不一致的数据。这是因为机上交易所做的更改是可见的。如果应用程序从尚未更新的实例化视图中读取,也可能会看到不一致。另一个缺点是用户必须检测并忽略重复事件。
实现原子性
在事件驱动的体系结构中,还存在自动更新数据库和发布事件的问题。例如,订单服务必须在ORDER表中插入一行并发布Order Created事件。这两项操作是以原子方式完成的。如果服务在更新数据库之后但在发布事件之前崩溃,则系统变得不一致。确保原子性的标准方法是使用涉及数据库和Message Broker的分布式事务。但是,由于上述原因,例如CAP定理,这正是我们不想做的。
使用本地事务发布事件
实现原子性的一种方式是让应用程序使用仅涉及本地事务的多步处理来发布事件。诀窍是在存储业务实体状态的数据库中有一个用作消息队列的EVENT表。应用程序开始一个(本地)数据库事务,更新业务实体的状态,将事件插入EVENT表并提交事务。单独的应用程序线程或进程查询EVENT表,将事件发布到Message Broker,然后使用本地事务将事件标记为已发布。下图显示了设计。
订单服务将一行插入到ORDER表中,并将一个Order Created事件插入到EVENT表中。Event Publisher线程或进程查询EVENT表以查找未发布的事件,发布事件,然后更新EVENT表以将事件标记为已发布。
这种方法有几个好处和缺点。其中一个好处是它保证每次更新都会发布一个事件,而不依赖于2PC。另外,应用程序发布业务级别的事件,这消除了推断它们的需要。这种方法的一个缺点是它有潜在的错误倾向,因为开发者必须记得发布事件。这种方法的局限性在于,由于它们的事务和查询能力有限,因此使用某些NoSQL数据库时实施具有挑战性。
这种方法通过让应用程序使用本地事务来更新状态和发布事件来消除对2PC的需求。现在让我们看看通过让应用程序简单更新状态来实现原子性的方法。
挖掘数据库事务日志
在不使用2PC的情况下实现原子性的另一种方式是事件由线程或进程发布,该线程或进程挖掘数据库的事务或提交日志。应用程序更新数据库,导致数据库的事务日志中记录更改。事务日志挖掘程序线程或进程读取事务日志并将事件发布到Message Broker。下图显示了设计。
这种方法的一个例子是开源的LinkedIn Databus项目。Databus挖掘Oracle事务日志并发布与更改相对应的事件。LinkedIn使用Databus来保持各种衍生数据存储与记录系统一致。
另一个示例是AWS DynamoDB中的流机制,它是托管的NoSQL数据库。DynamoDB流包含在过去24小时内对DynamoDB表中的项目进行的按时间排序的更改(创建,更新和删除操作)。应用程序可以从流中读取这些更改,并将其作为事件发布。
事务日志挖掘具有各种优点和缺点。其中一个好处是它可以保证每次更新都会发布一个事件,而不使用2PC。事务日志挖掘还可以通过从应用程序的业务逻辑中分离事件发布来简化应用程序。一个主要缺点是事务日志的格式是每个数据库专有的,甚至可能在数据库版本之间改变。此外,从事务日志中记录的低级更新对高级业务事件进行反向工程很难。
事务日志挖掘通过让应用程序执行一件事来消除对2PC的需要:更新数据库。现在让我们看看消除更新并仅依赖于事件的不同方法。
使用事件采购
事件采购通过使用完全不同的,以事件为中心的方法来持久化商业实体,从而在没有2PC的情况下实现原子性。应用程序不是存储实体的当前状态,而是存储一系列状态改变事件。应用程序通过重播事件来重建实体的当前状态。每当业务实体的状态发生变化时,新事件将附加到事件列表中。由于保存一个事件是一个单独的操作,它本质上是原子的。
要了解事件采购如何工作,请考虑订单实体作为示例。在传统方法中,每个订单映射到ORDER表中的一行以及ORDER_LINE_ITEM表中的行。但是在使用事件采购时,订单服务以状态变化事件的形式存储订单:创建,批准,发货,取消。每个事件都包含足够的数据来重建订单的状态。
事件存储在事件存储中,事件存储是一个事件数据库。该商店有一个用于添加和检索实体事件的API。Event Store的行为与我们前面介绍的体系结构中的Message Broker相似。它提供了一个API,使服务能够订阅事件。活动商店将所有活动提供给所有感兴趣的订户。Event Store是事件驱动的微服务架构的中坚力量。
事件采购有几个好处。它解决了实现事件驱动体系结构的关键问题之一,并且可以在状态发生变化时可靠地发布事件。因此,它解决了微服务架构中的数据一致性问题。而且,因为它坚持事件而不是域对象,所以它大多避免了对象 - 关系阻抗不匹配问题。事件采购还提供了对业务实体所做更改的100%可靠审计日志,并且可以实现在任何时间点确定实体状态的临时查询。事件采购的另一个主要优势是您的业务逻辑由交换事件的松散耦合的业务实体组成。这使得从单一应用程序迁移到微服务体系结构变得更加容易。
事件采购也有一些缺点。这是一种不同的,不熟悉的编程风格,所以有一条学习曲线。事件存储只能直接支持通过主键查找业务实体。您必须使用命令查询责任隔离(CQRS)来实施查询。因此,应用程序必须处理最终一致的数据。
概要
在微服务架构中,每个微服务都有自己的私有数据存储。不同的微服务可能使用不同的SQL和NoSQL数据库。虽然这种数据库体系结构具有显着的优势,但却造成了一些分布式数据管理挑战 第一个挑战是如何实现在多个服务之间保持一致性的业务事务。第二个挑战是如何实现从多个服务中检索数据的查询。
对于许多应用程序,解决方案是使用事件驱动的体系结构。实现事件驱动架构的一个挑战是如何自动更新状态以及如何发布事件。有几种方法可以实现这一点,包括将数据库用作消息队列,事务日志挖掘和事件源。
原文地址:https://www.nginx.com/blog/event-driven-data-management-microservices/