今天的每个人都在考虑和构建微服务。从微服务的核心原理和实际情况来看,它是一个分布式系统。
什么是分布式事务?
跨网络的多个物理系统或计算机上的事务简称为分布式事务。在微服务领域,事务现在被分发到多个服务中,这些服务被依次调用以完成整个事务。
下图是一个使用事务的单体电子商务系统:
在上面的系统中,如果用户向平台发送Checkout请求,则平台将创建在多个数据库表上工作的本地数据库事务,以处理库存中的订单和预留物料。如果任何步骤失败,则事务可以回滚,保留订单和物料。这称为ACID(原子性,一致性,隔离性,耐久性),由数据库系统保证。
下图是被分解为微服务的电子商务系统:
分解该系统时,我们创建了具有单独数据库的微服务OrderMicroservice和InventoryMicroservice。当用户发出Checkout请求时,这两个微服务都将被调用以将更改应用到他们自己的数据库中。因为该事务现在通过多个系统跨多个数据库,所以现在将其视为分布式事务。
微服务中的分布式事务有什么问题?
随着微服务架构的出现,我们正在失去数据库的ACID性质。事务现在可以跨越多个微服务,因此也可以跨数据库。我们将面临的关键问题是:
我们如何保持事务的原子性?
原子性意味着在事务中所有步骤都已完成或都没完成。在上面的示例中,如果InventoryMicroservice方法中的“reserve items”失败,我们如何回滚由OrderMicroservice应用的“process order”更改?
我们如何处理并发请求?
如果来自任何一个微服务的对象都同时保留在数据库中,那么另一个请求将读取同一对象。服务应该返回旧数据还是新数据?在上面的示例中,一旦OrderMicroservice完成并且InventoryMicroservice现在正在执行其更新,则客户下达的订单数量请求是否应包括当前订单?
可能的解决方案
在设计和构建基于微服务的应用程序时,以上两个问题非常关键。为了解决这些问题,描述了以下方法列表:
- 两阶段提交(Two-Phase Commit)
- 最终一致性和补偿(Eventual Consistency and Compensation / SAGA)
1.两阶段提交
顾名思义,这种处理事务的方式有两个阶段,即准备阶段和提交阶段。一个重要的参与者是保持事务生命周期的事务协调器(Transaction Coordinator)。
怎么运行的:
在准备阶段,所有涉及的微服务都准备提交并通知协调器它们已准备好完成事务。然后在提交阶段,事务协调器向所有微服务发出提交或回滚命令。
让我们以电子商务系统为例:
在上面的示例中,当用户发送结帐请求时,TransactionCoordinator将首先开始使用所有上下文信息进行全局事务。首先,它将向OrderMicroservice发送准备命令,以创建订单。然后,它将向库存微服务发送准备命令,以保留物料。当两个服务都可以执行更改时,它们将锁定对象以防止进一步更改,并通知TransactionCoordinator。一旦TransactionCoordinator确认所有微服务都准备好应用其更改,它将通过请求与事务的提交来要求它们保留其更改。此时,所有对象将被解锁。
在失败场景中(上图)-如果在任何时候单个微服务都无法准备,那么TransactionCoordinator将中止事务并开始回滚过程。在该图中,OrderMicroservice由于某种原因未能创建订单,但是InventoryMicroservice已答复它已准备好创建订单。TransactionCoordinator将请求InventoryMicroservice的中止,然后该服务将回滚所做的任何更改并解锁数据库对象。
优点
-
- 该方法保证了交易是原子的。事务将以所有微服务成功或所有微服务均未更改结束。
- 其次,它允许读写隔离,在事务协调器提交更改之前,对对象的更改不可见。
- 该方法是同步调用,其中将通知客户端成功或失败。
缺点
-
- 一切都不尽完美,与单个微服务的运行时间相比,两阶段提交的速度相当慢。它们高度依赖事务协调器,事务协调器在高负载下确实会降低系统速度。
- 另一个主要缺点是锁定数据库行。锁定可能会成为性能瓶颈,并且可能发生死锁,两个事务相互锁定。
2.最终一致性和补偿
关于最终一致性的最佳定义之一是在microservices.io上进行了描述:每个服务在更新数据时都会发布一个事件。其他服务订阅事件。收到事件后,服务将更新其数据。
在这种方法中,分布式事务由相关微服务上的异步本地事务完成。微服务通过事件总线相互通信。
怎么运行的:
再次,以电子商务系统为例:
在上面的示例(上图)中,客户端请求系统处理订单。根据该请求,编排人员将发出一个事件Create Order,以标记事务开始。OrderMicroservice侦听此事件并创建一个订单,如果成功,它将发出一个Order Created事件。编舞人员侦听此事件,并通过发出“ Reserve Items”事件来保留项目。InventoryMicroservice会侦听此事件并保留项目,如果成功,则会发出Items Reserved事件,在此示例中表示交易结束。
微服务之间的所有基于事件的通信都是通过事件总线发生的,并由另一个系统进行编排以解决复杂性问题。
如果由于某种原因,InventoryMicroservice未能保留项目(上图),则会发出“无法保留项目”事件。编舞人员侦听此事件,并通过发出Delete Order事件来启动补偿事务。OrderMicroservice侦听此事件并删除创建的订单。
优点
这种方法的一大优势在于,每个微服务仅专注于自己的原子事务。如果其他服务需要更长的时间,则不会阻止微服务。这也意味着不需要数据库锁定。由于基于异步事件的解决方案,使用此方法可使系统在重负载下具有高度可伸缩性。
缺点
主要缺点是该方法没有读取隔离。这意味着,在上面的示例中,客户可以看到订单已创建,但是在下一秒钟,由于补偿交易,订单被删除。而且,当微服务数量增加时,调试和维护变得更加困难。
结论
当由于一个事件而需要在两个位置更新数据时,与两阶段提交相比,最终一致性/ SAGA方法是处理分布式事务的一种更好的方法。主要原因是两阶段提交无法在分布式环境中扩展。最终一致性方法还引入了一系列新问题,例如如何自动更新数据库并发出事件。采用这种方法需要改变开发团队和测试团队的思维方式。