学习文章:https://www.nginx.com/blog/event-driven-data-management-microservices/
场景
整体应用程序通常具有单个关系数据库。使用关系数据库的主要好处是您的应用程序可以使用ACID事务,这提供了一些重要的保证:
- 原子性–原子地进行更改
- 一致性–数据库状态始终是一致的
- 隔离–即使事务是同时执行的,看起来它们还是串行执行的
- 耐用性–交易一旦提交,便不会撤消
结果,您的应用程序可以简单地开始事务,更改(插入,更新和删除)多行并提交事务。
第一个挑战是如何实现在多个服务之间保持一致性的业务交易。
客户服务维护有关客户的信息,包括他们的信用额度。订单服务负责管理订单,并且必须验证新订单没有超过客户的信用额度。
image.png第二个挑战是如何实现从多个服务检索数据的查询。例如,假设应用程序需要显示一个客户及其最近的订单。如果订单服务提供了用于检索客户订单的API,那么您可以使用应用程序侧联接来检索此数据。该应用程序从客户服务检索客户,从订单服务检索客户的订单。但是,假设Order Service仅支持按主键查找订单(也许它使用的NoSQL数据库仅支持基于主键的检索)。在这种情况下,没有明显的方法来检索所需的数据。
事件驱动架构
微服务会在发生显着事件(例如更新业务实体)时发布事件。其他微服务订阅了这些事件。当微服务收到事件时,它可以更新自己的业务实体,这可能导致发布更多事件。
每个步骤都包含一个微服务,该微服务更新业务实体并发布触发下一步的事件。以下图表序列显示了在创建订单时如何使用事件驱动的方法来检查可用信用。微服务通过消息代理交换事件。
客户服务部门使用“已创建订单”事件,为订单保留信用,并发布“已保留信用”事件。
image.png
订单服务使用“保留信用”事件,并将订单状态更改为“打开”。
image.png
每个服务自动更新数据库并发布事件(稍后再进行),并且(b)Message Broker保证事件至少被传递一次,则您可以实现跨多个服务的业务交易。重要的是要注意,这些不是ACID交易。它们提供的保证要弱得多,例如最终的一致性。此事务处理模型已称为BASE模型。
事件驱动的体系结构有几个优点和缺点。它使跨多个服务的事务的实现成为可能,并提供最终的一致性。另一个好处是它还使应用程序能够维护实例化视图。一个缺点是,编程模型比使用ACID事务时更复杂。通常,您必须实施补偿事务以从应用程序级故障中恢复;例如,如果信用检查失败,则必须取消订单。此外,应用程序必须处理不一致的数据。这是因为可以看到机内交易所做的更改。如果应用程序从尚未更新的实例化视图中读取数据,则还会看到不一致之处。另一个缺点是订户必须检测并忽略重复的事件。
实现原子性
还存在原子更新数据库并发布事件的问题。例如,订购服务必须在ORDER表中插入一行并发布订购创建事件。这两个操作必须原子完成。如果服务在更新数据库之后但在发布事件之前崩溃,则系统会变得不一致。确保原子性的标准方法是使用涉及数据库和Message Broker的分布式事务。但是,由于上述原因(例如CAP定理),这正是我们不希望做的。
使用本地事务发布事件
实现原子性的一种方法是,应用程序使用仅涉及本地事务的多步骤过程来发布事件。
诀窍是在存储业务实体状态的数据库中具有一个EVENT表,该表充当消息队列。
1.应用程序开始(本地)数据库事务,更新业务实体的状态,将事件插入EVENT表,然后提交事务。
2.单独的应用程序线程或进程查询EVENT表,将事件发布到Message Broker,然后使用本地事务将事件标记为已发布。
一个好处是,它可以确保每次更新都发布一个事件,而无需依赖2PC。此外,该应用程序还发布业务级别的事件,从而无需推断它们。
一个缺点是,由于开发人员必须记住要发布事件,因此它很容易出错。这种方法的局限性在于,由于它们的事务和查询功能有限,因此在使用某些NoSQL数据库时难以实施
一种通过使应用程序简单地更新状态来实现原子性的方法。
挖掘数据库事务日志
在没有2PC的情况下实现原子性的另一种方法是
事件由挖掘数据库事务或提交日志的线程或进程发布。该应用程序更新数据库,这导致更改被记录在数据库的事务日志中。事务日志挖掘器线程或进程读取事务日志并将事件发布到Message Broker。
这种方法的一个示例是开源LinkedIn Databus项目。Databus挖掘Oracle事务日志并发布与更改相对应的事件。LinkedIn使用Databus来保持各种派生数据存储与记录系统一致。
另一个示例是AWS DynamoDB中的流机制,这是一个托管的NoSQL数据库。DynamoDB流包含过去24小时内对DynamoDB表中的项目进行的按时间顺序排列的更改(创建,更新和删除操作)序列。应用程序可以从流中读取这些更改,例如,将其作为事件发布。
一个好处是,它保证了每次更新都可以发布事件,而无需使用2PC。事务日志挖掘还可以通过将事件发布与应用程序的业务逻辑分开来简化应用程序。
一个主要的缺点是事务日志的格式是每个数据库专有的,甚至可以在数据库版本之间进行更改。同样,可能很难从事务日志中记录的低级更新中对高级业务事件进行反向工程。
事务日志挖掘通过让应用程序执行一件事来消除对2PC的需求:更新数据库
让我们看一下消除更新并仅依赖事件的另一种方法。
使用事件源
通过使用根本不同的,以事件为中心的方法来持久化业务实体,事件采购无需2PC就可以实现原子性。
该应用程序不是存储实体的当前状态,而是存储一系列状态更改事件。
该应用程序通过重播事件来重建实体的当前状态。
只要业务实体的状态发生变化,就会在事件列表中附加一个新事件。
由于保存事件是单个操作,因此它本质上是原子的。
以Order实体为例。在传统方法中,每个订单都映射到ORDER表中的一行以及例如ORDER_LINE_ITEM表中的行。但是,在使用事件源时,订单服务以其状态更改事件的形式存储订单:已创建,已批准,已发货,已取消。每个事件都包含足够的数据来重构订单状态。
image.png事件保留在事件存储中,该事件存储是事件的数据库。
该商店具有用于添加和检索实体事件的API。
在我们之前描述的体系结构中,
事件存储的行为也类似于消息代理。它提供了使服务能够订阅事件的API。事件存储将所有事件传递给所有感兴趣的订户。事件存储是事件驱动的微服务体系结构的骨干。
事件源有几个好处。它解决了实现事件驱动的体系结构中的关键问题之一,并使得在状态改变时可靠地发布事件成为可能。结果,它解决了微服务体系结构中的数据一致性问题。另外,由于它保留事件而不是域对象,因此它可以避免对象关系阻抗不匹配的问题。事件源还提供了对业务实体所做的更改的100%可靠的审核日志,并使得可以实施临时查询来确定实体在任何时间点的状态。事件源的另一个主要优点是您的业务逻辑由交换事件的松散耦合的业务实体组成。这使得从单片应用程序迁移到微服务架构变得容易得多。
事件源也有一些缺点。这是一种不同且陌生的编程风格,因此存在学习曲线。事件存储区仅直接支持通过主键查找业务实体。您必须使用命令查询职责隔离(CQRS)来实现查询。结果,应用程序必须处理最终一致的数据。
第一个挑战是如何实现在多个服务之间保持一致性的业务交易。第二个挑战是如何实现从多个服务检索数据的查询。
对于许多应用程序,解决方案是使用事件驱动的体系结构。实现事件驱动的体系结构的一个挑战是如何原子更新状态以及如何发布事件。有几种方法可以做到这一点,包括将数据库用作消息队列,事务日志挖掘和事件源。
网友评论