微服务架构中的分布式事务全面详解 -DZone微服务

21-01-07 banq

本文探讨在微服务架构中实现事务处理时出现的挑战以及用于处理它们的可能解决方案。

当从单体或整体架构迁移到微服务架构(MSA)时,处理分布式系统带来的复杂性是一项挑战。事务处理是此问题的重点。使用本地事务在Web应用程序中完成的典型数据库事务现在是一个复杂的分布式事务问题。在本文中,我们将讨论导致这种情况的原因,可能的解决方案以及使用MSA开发安全交易软件系统的最佳实践。

如果您已经熟悉了数据库事务背后的相关概念以及分布式系统中数据一致性的复杂性,则可以跳到“微服务体系结构中的数据建模”部分,我们将探讨如何在现实世界中使用数据建模案件。

 

数据库事务:入门

在我们良好的旧式整体应用程序中,我们进行了数据库事务以实现全有或全无的数据操作,同时保持数据的一致性。我们主要使用ACID事务,这是您在关系数据库系统中找到的。这是快速复习: 

  • 原子性:所有操作都成功执行,或者所有操作一起失败。
  • 一致性:数据库中的数据按照其规则(例如参照完整性)规定保持有效状态。
  • 隔离:并发运行的分离事务不能相互干扰。应该是因为事务在其自己的隔离环境中运行,在该环境中其他事务无法看到该持续时间内发生的更改。
  • 耐用性:提交事务后,更改将被持久存储,例如持久存储在磁盘中,因此数据库服务器的临时崩溃不会丢失数据。 

我们可以想象使用ACID事务,如下所示,我们将一些资金从一个帐户转移到另一个帐户。

的SQL

BEGIN TRANSACTION
  UPDATE ACCOUNT SET BALANCE=BALANCE-AMOUNT WHERE ID=1;
  UPDATE ACCOUNT SET BALANCE=BALANCE+AMOUNT WHERE ID=2;
END TRANSACTION

在这里,我们将单个借记和贷记操作包装在ACID事务中。这样可以避免出现不一致的情况,例如,如果资金从一个帐户中取出而没有放入另一个帐户中,则系统中会损失金钱。这是一个清晰,直接的解决方案,我们将在需要时继续编写此类代码。 

我们习惯了在需要时享受ACID事务的奢华。对于将处理需求保存​​在单个数据库服务器中的大多数典型用户,此模型很好。但是对于需要根据对数据访问,存储容量和读/写扩展的不断增长的需求来扩展系统的人们而言,这种架构风格很快就会瓦解。对于这些用户,有两种扩展数据存储的方式:

  • 垂直扩展:将功能更强大的硬件放在单个服务器上,例如增加CPU和RAM或移至大型计算机。通常这比较昂贵,并且在无法再升级硬件时将达到极限。
  • 水平扩展:添加了更多的服务器节点,并且所有内容都通过计算机网络进行了连接。这通常是最实际的操作。 

在水平扩展中,由于数据存储在集群中具有多个节点,因此事情变得有些复杂。由于数据驻留在物理上分开的服务器中,因此出现了一系列新的挑战。CAP定理对此进行了解释,该定理说在分布式数据存储中只能实现以下两个属性。

  • 一致性:这与节点之间的数据一致性有关。数据将具有副本,以在出现故障的情况下提供冗余并提高读取性能。这意味着当在单个位置完成数据更新时,应在所有其他副本中同时更新数据,而不会给客户端造成任何延迟。这在形式上更具有线性化性。如您所见,这与ACID中的一致性概念不同。
  • 可用性:分布式数据存储具有很高的可用性,因此服务器实例的丢失不会妨碍整体数据存储的功能,并且用户仍将获得对其请求的无错误响应。 
  • 分区容限:数据存储区可以处理网络分区。网络分区是网络中某些节点之间的通信丢失。因此,某些节点可能无法与数据存储群集中的其他节点通信。从数据库集群中的节点的角度来看,这有效地将数据存储节点分区到多个本地网络。外部客户端仍然可以访问数据库群集中的所有节点。 

因此,不可能同时具有一致性,可用性和分区容限。如果我们考虑可能发生的情况,我们可以直观地理解这种行为。

为了在写入数据时保持一致性,我们需要同时写入所有充当副本的服务器。但是,如果网络中有分区,我们将无法执行此操作,因为那时我们只能访问某些服务器。在这种情况下,如果我们想在保持一致性的同时容忍这些分区,就不能让用户从数据存储中读取不一致的数据。这意味着我们需要停止响应用户请求,从而使数据存储不再可用(一致且分区容忍)。另一种情况是让数据存储仍然起作用,即保持其可用状态,这将使其不再一致(可用且具有分区容忍性)。 

最终的情况是系统不容许分区保持一致且可用。在这种情况下,为了具有强一致性(线性化),我们仍然必须使用事务协议(例如两阶段提交(2PC))在复制的数据库服务器节点之间执行数据操作。在2PC中,参与事务的数据库的操作由事务协调器在以下两个阶段执行:

  • 准备:要求参与者检查其数据操作是否可以执行,并向事务协调员提供保证,以后如果需要,可以提交操作或回滚。这是为所有参与者完成的。 
  • 提交:如果所有参与者在准备阶段都表示可以,则为每个参与者提供一个提交操作,以执行前面提到的操作。在这一点上,由于较早地给予事务协调员的承诺,参与者无法拒绝该操作。如果任何参与者较早地由于任何原因拒绝了其本地数据操作,则坐标将向所有参与者发送回滚命令。 

除了上述用于可伸缩性的数据库复制方案之外,在不同类型的系统(例如数据库服务器和消息代理)之间执行事务时,还将使用2PC。但是,由于分布式参与者中锁争用的增加,我们通常避免使用2PC,这会导致性能下降并阻碍可伸缩性。 

实际上,计算机网络并不可靠,我们应该期望它们一次或一次具有网络分区。因此,我们通常会看到优先考虑可用性或一致性的数据库系统,即AP或CP。一些系统允许用户调整这些参数以使其高度可用或选择一致性级别。数据库系统(例如Amazon的DynamoDB和Apache Cassandra)中提供了此功能。但是,由于它们不具有严格的CAP级别一致性,因此通常被归类为AP系统。Cassandra的轻量级事务支持使用Paxos共识协议来实现可线性化的一致性,但这很少使用,因为它的机制需要非常低的性能,这是可以预期的。

  

可伸缩性对数据一致性的影响

当我们检查CAP定理的权衡时,如果我们重视高可用性以及高性能和可伸缩性,则必须在数据一致性方面做出折衷。CAP中的此数据一致性属性会影响ACID的隔离属性。如果分布式系统中节点之间的数据不一致,则意味着存在事务隔离问题,我们可以看到脏数据。因此,在这种情况下,我们必须接受现实并找到一种无需ACID事务和完全CAP一致性的生活方式。

拥抱最终一致性的这个事务的一致性模型也被称为CAP定理,这促进了是可用最终一致性。软状态表示数据可能会由于最终的一致性而在以后更改。大多数NoSQL数据库都遵循这种方法,在这种方法中,它们不提供任何ACID事务功能,而是专注于可伸缩性。 

在许多用例中,由于不需要严格的数据一致性,最终的一致性还可以。例如,域名系统(DNS)基于最终的一致性模型。多个中间缓存包含DNS条目。如果有人更新了DNS条目,则不会立即更新这些条目,而是在对本地条目执行缓存超时后执行DNS查询。由于不经常更新DNS条目,因此对每个名称解析进行新的DNS查询只是过大,并且将成为主要的网络性能瓶颈。因此,在DNS中为用户提供过时的条目是可以容忍的。同样,在许多其他现实世界中,我们也会这样做。我们将实施其他变通办法,以检测这种类型的过时数据或不一致之处,并在此时采取适当的措施, 

在我们的分布式数据存储方案中,一致性级别取决于我们正在实现的用例。让我们更详细地了解存在的各种一致性级别,从最高到最低的一致性级别。

  • 严格的可串行化

在严格的可串行化性中,应该在所有副本中自动进行多个对象操作,同时保持实时排序。实时排序意味着它与客户端针对每个人都共享的全局时钟执行的操作相同。这些分组的对象操作代表单个事务。

这是实现ACID事务的隔离方面所必需的。因此,如果我们需要在ACID事务中发现典型行为以应对工作量,则在分布式数据存储中需要严格的可串行化性。 

  • 线性化

在此一致性模型中,整个副本中单个对象的操作应该是原子的。当单个客户端看到对副本中的对象执行的操作时,连接到任何其他副本的任何其他客户端也应看到相同的操作。同样,对对象执行操作的顺序应与所有客户端所看到的顺序相同,并且类似于相同的实时顺序。 

什么时候需要?假设我们有三个客户/进程。进程A将对象值写入数据存储。进程B收到一些外部事件,提示它读取上述对象值。读取该值后,它将向进程C发送一条消息,以读取对象值并做出决定。对过程B的理解是,它读取的对象值将至少是它具有的最新值,而不是旧值。为了确保这种行为,分布式数据存储必须提供线性化一致性保证,以确保由进程A完成的更新可同时被所有其他副本立即看到。 

即使我们使用基于仲裁的读/写之类的方法将Cassandra数据库配置为具有强一致性,也不会提供线性化保证,因为更新不是原子性地在副本中进行的。为了实现线性化,我们必须在Cassandra中使用轻量级事务支持。 

  • 顺序一致性

在顺序一致性方面,在数据存储上执行操作的流程将以与其他流程相同的顺序出现。同样,所有操作的这种顺序将与每个流程操作的相对顺序一致。基本上,它按操作的整体顺序保留了流程级别的顺序。

  • 因果一致性

在因果一致性方面,所有潜在因果相关操作应以相同顺序显示在所有流程中。简而言之,如果您基于先前观察到的单独操作进行操作,则这些操作的顺序对于其他进程也必须相同。 

为了满足因果一致性,应支持以下行为:

  • 读取您的写入内容:一个进程应该能够立即读取它在早期写入操作中所做的更改。 
  • 单调读取:对对象执行读取操作的进程应始终看到相同或更新的值。基本上,读取操作无法返回并看到较旧的值。 
  • 单调写:执行写操作的进程应确保其他进程应按其相对顺序查看这些写操作。 
  • 写跟随读取:进程通过基于较早的写操作读入较早的值v1,将新值v2写入对象。在这种情况下,每个人都应该始终在v2之前看到对象的值v1。

这是在许多实际实际应用中特别有用的一致性模型。例如,让我们来看看一个由分布式数据存储支持的社交媒体网站。无论网站是更新其个人资料状态还是评论某人的个人资料状态,我们都有并发用户与该网站进行交互。刚刚发生碰撞小事故的用户Anne的状态为“发生小事故,正在等待X-ray结果!”。处于此状态后,她即可获得结果,并且没有骨折。因此,她将自己的状态更新为“好消息:没有骨折!”。鲍勃(Bob)看到了来自安妮(Anne)的最后一条消息,并回复她的状态说:“很高兴听到!:)”。 

在上述情况下,如果我们的数据存储至少提供因果一致性,则所有其他用户将以正确的顺序查看来自Anne和Bob的消息。但是,如果数据存储不提供因果一致性,则其他用户可能会看到Anne的第一条消息,然后看到Bob的消息,而看不到Anne的第二条更新。这种情况变得有些奇怪,因为鲍勃似乎对安妮的不幸表示高兴,但事实并非如此。因此,在这种情况下,我们需要因果一致性。 

因此,假设在相同情况下我们有一个因果关系一致的数据存储,那么假设Tom更新他的状态为“我刚买了第一辆车!” 就在安妮(Anne)第一次更新之前。网站的某些用户在安妮的第一条消息之后看到了他的状态。这种情况很好,因为与Tom的更新发生在现实中的Anne更新之前还是之后无关紧要。它们之间没有联系,即汤姆的行为不是由安妮的行为引起的。没有因果关系的其他操作以最终一致的方式操作。 

MongoDB是专门支持因果一致性的分布式数据存储。它基于Lamport逻辑时钟实现了这一点。 

  • 最终一致性

在最终的统一性中,如果不再对数据存储进行写操作,则数据存储中的所有副本都将收敛并最终商定最终值。它不会提供任何其他保证,例如因果一致性,直到它稳定在最终值上。 

实际上,这种一致性模型适用于用例,这些用例只是并发值更新,而值在更新时之间没有任何联系。用户并不关心中间值,而只关心最终的最终稳定值。例如,让我们建立一个发布每个城市当前温度的网站。这些值会不时变化。在某些时候,某些用户可能会看到最新的温度值,而其他用户仍未更新。但是最终,数据更新将被网站的所有用户追上。因此,只要最终所有用户最终都将看到相同的温度值,存储这些值的分布式数据库的传播延迟就不会成为大问题。

有关事务一致性模型的更多详细信息,请检查本文结尾的资源部分。

现在,我们对与事务处理和一致性模型有关的方面有了一般的了解。当您在任何分布式处理环境(例如MSA)中工作时,此知识很有用。我们所讨论的相同概念也将适用于此。现在让我们看看如何在MSA中对数据建模。 

 

微服务架构中的数据建模

微服务的基本要求是高度凝聚力和松散耦合。这自然是必需的,因为开发团队的组织结构也将围绕此概念构建。将有单独的团队负责微服务,他们需要灵活性和自由才能独立于其他人。这意味着他们避免在设计和实现的内部细节上与其他团队进行不必要的同步。 

根据这些要求,微服务应严格禁止共享数据库。如果每个微服务都不能拥有自己的数据库,那么这很好地表明了这些微服务需要合并。 

下面显示了电子商务后端可能的微服务设计。 

在这里,我们使用自己的微服务来管理系统的各个方面。在我们开始处理事务之前,这看起来不错。典型的操作将包括使用一组产品创建用户订单。使用库存服务检查这些产品的可用性,并在完成订单后更新库存以减少那些产品的可用库存。在典型的整体应用程序中,您可以在单个ACID事务中执行以下操作。 

的SQL

BEGIN TRANSACTION

  CHECK INVENTORY OF PRODUCT 1 FOR 5 ITEMS
  CHECK INVENTORY OF PRODUCT 2 FOR 10 ITEMS
  CREATE ORDER
  ADD 5 ITEMS OF PRODUCT 1 TO ORDER
  ADD 10 ITEMS OF PRODUCT 2 TO ORDER
  DECREMENT INVENTORY OF PRODUCT 1 BY 5 ITEMS
  DECREMENT INVENTORY OF PRODUCT 2 BY 10 ITEMS
  INSERT PAYMENT 
END TRANSACTION

通过这种方法,我们相信数据存储在数据操作之后最终会保持一致状态。现在,我们如何使用微服务对上述操作建模?可能想到的一种解决方案是以下方法。 

在这里,协调器服务“ Admin”通过调用每个服务的操作来创建服务编排。如果所有操作执行没有任何问题,这将起作用。但是,流程中的某些步骤很有可能会失败,例如,当用户的信用额度不足或网络通信失败时,如果发生应用程序错误。例如,如果由于用户管理服务对于支付处理服务不可用而导致流程失败,则步骤4将失败。但是目前,我们已经创建了一个订单并更新了库存。因此,现在我们的系统处于不一致状态,在该状态下,库存报告的商品数量减少了,而没有人购买!此处的明显问题是,我们没有在单个事务中执行这些操作,如果一步失败, 

解决我们的问题有哪些可能的解决方案?最简单的解决方案将是通过将所有这些操作放到单个服务和单个数据库中,并在本地事务中完成所有操作来返回整体解决方案。但是在这种情况下,我们假设已经做出了大型单片应用程序无法扩展的决定,并且必须将其分解为单个微服务。

在这种情况下,对于我们的事务问题,我们只剩下基于2PC的解决方案。我们可以使用WS-TXBallerina分布式事务解决方案在网络服务之间执行基于2PC的全局事务的功能。如果您需要在事务中使用ACID担保,则只有类似的方法才是选择。但是,应谨慎使用,因为典型的2PC缺点(如增加后端数据库中的锁定时间)仍然存在。由于额外的网络通信跃点,这种性质仅在微服务环境中有所增加。 

但是,大多数现实工作流程不需要ACID保证,因为可以使用与操作相反的操作来逆转错误的操作。因此,在订单处理工作流程中,如果出现问题,我们可以为已完成的操作执行补偿操作并回滚整个事务。这些操作将包括诸如将付款退回到用户的信用卡,通过增加订单中产品的数量来更新产品库存以及更新已取消的订单记录。 

实际上,我们的电子商务后端方案不能完全建模为单个数据库事务,因为处理付款的操作是使用外部付款网关完成的,该网关不能是本地或全局(2PC)数据库事务的一部分。但是,在基于2PC的全局事务中,这种情况是一个例外。在2PC场景中,全局事务的最后一个参与者不必同时执行准备阶段和提交阶段,但是单个准备操作就足以执行其操作。这称为最后一次资源提交优化。同样,这仅在这种特定情况下才有可能,在这种特定情况下,这种类型的参与者在工作流中任何其他位置都将无法进行全局事务。 

因此,现在我们决定不再需要使用2PC获得的严格数据一致性,并且以后可以更正所有问题。让我们看一下该工作流程的可能执行方式。 

在这里,工作流在步骤4失败。从那以后,管理服务应以与先前调用的服务相反的顺序执行一组补偿操作。但是这里有一个潜在的问题。如果管理服务在还原操作时遇到错误(例如临时网络问题)怎么办?我们再次遇到了数据不一致的问题,其中未完成总回滚,也无法知道我们上次执行的操作以及以后如何解决。 

处理此问题的一种方法是保留由admin服务完成的操作的日志。这可能类似于以下内容:

  • TX1:检查库存
  • TX1:创建订单
  • TX1:更新库存
  • TX1:流程付款-失败
  • TX1:取消标记订单
  • TX1:更新库存-库存增加计数

因此,管理服务可以跟踪已执行的操作以及未执行的操作。但是同样,我们必须考虑即使在处理这样的事件日志时也可能发生的极端情况。管理服务及其日志与其他远程服务操作是分开的,因此,这些交互本身无法进行事务处理。有如下更改:

  • 管理员服务执行库存服务以还原库存计数(通过递增)
  • 管理员服务使用“ TX1:更新库存-库存增加计数”更新其日志

如果执行上面的第一个操作,然后服务在第二个操作之前崩溃,该怎么办?当admin服务再次继续其操作时,它将认为它没有执行库存恢复操作,并将再次执行第一个操作。这将使库存数据具有无效值,因为它使库存数增加了两次,这不好!正如我们在分布式系统中经常看到的那样,这是一次交付最少的情况。解决此问题的常用方法是将我们的操作建模为幂等的。也就是说,即使多次执行相同的操作,也不会造成任何损害,并且目标系统的状态将相同。 

但是我们的库存回滚操作不是幂等的,因为它没有设置特定的值,而是在目标系统中递增已经存在的值。因此,您无法复制这些操作。通过直接设置订单之前的存货盘点,我们可以使其成为幂等操作。但是再说一次,由于我们的事务在微服务体系结构中建模的方式,它没有提供您在ACID事务中会发现的任何类型的隔离属性,即严格的序列化一致性级别。也就是说,当执行我们的事务时,另一个用户也可能正在创建另一个涉及相同产品的订单,这将修改相同的库存记录。因此,这两项事务的操作可能会重叠并导致不一致的情况。 

因此,实际上,不仅在事务回滚的情况下,即使在两个并发成功的事务中,由于缺乏隔离,幂等操作也可能导致数据丢失更新。让我们检查下面两个事务操作的时间表。 

在这里,我们有两个事务TX1和TX2。他们俩都在为产品“ P1”下订单,该产品的初始库存为100。TX1将创建一个包含10个项目的订单,然后TX2将创建一个包含20个项目的订单。从上面的序列图中可以看出,检查库存和更新库存不是在两个事务中原子发生的,而是它们相互交错,最终TX2的库存更新使TX1的库存更新蒙上了阴影。因此,最终P1的产品数量为80,而由于两次交易事务购买了30种产品,应该为70。因此,现在库存数据库数据错误地发出了实际可用库存的信号。

由于两个并发进程未正确隔离,因此我们出现了竞争状况。解决这种情况的方法是,使库存更新操作相对于数据库中的现有值进行值递减操作,即decrementInventoryStockCount(product,offset)。因此,可以使用单个SQL操作或正在执行的单个本地事务将该操作作为目标服务中的原子操作。 

因此,通过此更新,可以按照以下方式重写上面的交互。 

正如我们现在所看到的,随着新操作减少库存中的盘点,我们的最终盘点将保持一致和正确。 

注意:我们仍然会有不同的错误情况,在检查初始库存计数后,在结帐时,其他事务已经带来了库存的产品库存可能是空的。这只是作为业务流程错误处理,我们可以回滚操作,并且数据仍然是一致的。 

有时由于我们在微服务通信中遇到的事务隔离问题,使数据操作成为幂等是不可行的。这意味着,如果不确定某个操作是否在远程微服务中执行,就不能盲目地重试事务中的操作。这可以通过具有唯一标识符(例如微服务调用操作的事务ID)来解决,因此目标微服务将创建针对该微服务执行的事务历史。以这种方式,对于每个微服务操作调用,它可以执行本地事务以检查历史记录,并查看该事务是否已被执行。如果不是,它将执行数据库操作,并且仍在本地事务中,同时也更新事务历史记录表。以下代码显示了可能的实现使用以上策略在库存服务中执行decrementInventoryStockCount操作。 

function decrementInventoryStockCount(txid, pid, offset) {
   transaction {
       tx_executed = check transaction table record where id=txid
       if not tx_executed {
           prod_count = select count from inventory where product=pid
           prod_count += offset
           set count=prod_count in inventory where product=pid
           insert to transaction table with id=txid
       }
   }
}

 

微服务和消息传递

因此,现在,我们已经找到了一种一致的方式来执行一组微服务中的事务,并最终实现了全有或全无的保证。在此流程中,我们仍然必须维护事件日志并在可靠的持久性存储中对其进行更新。如果运行业务流程的协调服务发生故障,则另一个实体将不得不再次触发它以检查事件日志并执行任何恢复操作。如果我们已经有了一些可用于在服务之间提供可靠通信以帮助完成此任务的中间件,那将是很好的。 

这是事件驱动架构(EDA)有用的地方。这可以使用消息代理在微服务之间进行通信来创建。使用这种模式,我们可以确保是否成功将消息发送给旨在针对某些服务的消息代理,该消息将在某个时候成功发送给目标收件人。这种保证使我们的其他流程更容易建模。此外,由于消息代理的异步通信模型可以同时进行读取和写入操作,因此其开销要小一些,并且可以减少往返调用的等待时间,因此可以提供更好的性能。错误处理也更加简单,因为即使目标服务关闭,消息代理也会保留消息并在目标端点可用时将其传递。另外,它可以执行其他操作,例如重试故障和使用多个服务实例进行负载平衡请求。这种模式还鼓励服务之间的松散耦合。通信通过队列/主题进行,并且生产者和消费者不需要显式地彼此了解。的在基于补偿操作的微服务中实现事务时,Saga模式遵循这些通用准则。 

有两种实现该模式的协调策略:编排和编排。 

  • 编舞

在这种方法中,服务本身知道操作流程。在将初始消息发送到服务操作后,它将生成下一条消息,该消息将发送到后续服务操作。服务需要对事务流有明确的了解,从而导致服务之间的更多耦合。下图显示了将事务工作流实现为编排时服务与队列之间的典型交互。 

在这里,我们可以看到流程从客户端开始,该客户端通过其输入消息队列将初始消息发送到第一个服务。按照其业务逻辑,它可以在服务内定义的本地事务中执行与数据相关的操作。完成操作后,整个工作流程会将消息添加到编排中下一个服务的请求队列中。以这种方式,整个事务上下文将通过这些消息传播到每个服务,直到完成。 

如果工作流中的服务失败,我们需要回滚整个事务。为此,从发生故障的服务开始,它将清理其资源,并通过补偿队列将消息发送到恰好之前执行过的服务。这将移动上一步的执行,执行任何补偿操作以回滚其本地事务所做的更改,并重复联系其先前服务以进行补偿操作的操作。这样,错误处理链将到达第一个服务,然后该服务最终将消息发送到响应队列。它已连接到客户端,以通知发生了错误,并且已使用补偿操作成功回滚了总事务。 

从同步服务调用方法可以看出,在各自服务中执行本地事务时,我们应维护事务历史记录表,以确保在服务接收重复消息的情况下不重复本地操作。同样,为了不丢失工作流的连续性,服务仅应在完成数据库事务并将下一条消息添加到后续服务的请求队列之后,才从其请求队列中确认该消息。此流程确保我们不会丢失任何消息,并且通过成功执行或回滚所有操作来完成整个事务的执行。 

  • 编排

在这种协调方法中,我们拥有专用的协调器服务,该服务连续调用其他服务。协调器服务与其他服务之间的通信将通过请求和响应队列进行。这种模式类似于我们电子商务场景中的“管理”服务。唯一的变化是使用消息传递进行通信。 

协调器服务与其他服务之间的异步通信使它可以将事务处理建模为状态机,其中,用服务完成的每个步骤都可以更新状态机。状态机应保留在数据库中,以从协调服务的任何故障中恢复。下图显示了如何使用消息驱动策略设计业务流程协调方法。 

与编排相比,编排在服务之间的耦合较小。这是因为工作流由协调服务驱动,并且在给定时刻的完整状态保留在该服务本身中。但是,这里的服务并不是完全独立的,因为它们的请求和响应都绑定到特定的队列,固定的生产者和固定的消费者正在使用这些队列。因此,现在将这些服务用作通用服务变得更加困难。 

当操作次数较少时,基于编排的协调可能是可行的。对于复杂的操作,在对操作进行建模时,基于业务流程的方法将具有更大的灵活性。

实施此策略时,重要的是在开发人员框架中抽象出更详细的通信细节,状态机的持久性等。否则,开发人员最终将编写更多代码来实现事务处理,而不是核心业务逻辑。而且,如果典型的开发人员总是从头开始实施此模式,则容易出错。

 

选择微服务的事务模型

在实现事务时使用的任何技术中,我们都需要明确说明每种方法所能提供的数据一致性。然后,我们必须交叉检查我们的业务需求,以了解最适合我们的业务。以下可以用作一般准则。 

  • 2PC:在使用不同的编程语言/框架/数据库以及可能来自不同公司的开发团队创建微服务的情况下,不可能将所有操作组合到一个服务中。而且,这将要求严格的数据一致性,在这种情况下,不能容忍与业务需求相关的任何数据隔离问题,例如脏读。  
  • 基于补偿的事务:这是使用事务协调机制来跟踪事务中的各个步骤,并在失败的情况下执行补偿操作以回滚操作。这通常应涉及使用幂等数据操作或可交换更新,以便处理消息重复的情况。您的业​​务需求应该能够处理最终的一致性行为,例如脏读。 
  • 合并服务:如果由于性能问题和可伸缩性而不能容忍2PC,请使用此功能,但是对于业务需求需要严格的数据一致性。在这种情况下,我们应该将相关功能分组到自己的单个服务中,并使用本地事务。

 

概要

在本文中,我们研究了从ACID保证到使用BASE放松数据一致性保证的事务处理基础知识,以及CAP定理如何定义分布式系统中的数据存储权衡。然后,我们分析了分布式数据存储和一般分布式过程中可以支持的不同数据一致性级别。这些数据一致性问题直接适用于在MSA中对事务建模,在此我们需要将各个独立的服务整合在一起以进行全局事务。 

在选择与业务需求相关的选项时,我们研究了每种方法的优点和缺点,并检查了一般准则。 

 

1
猜你喜欢