当咱们向微服务架构迁徙时,如何解决好分布式事务是必须思考的问题。这篇文章介绍了分布式事务处理的两种计划,能够结合实际采纳适合的解决方案。原文:Handling Distributed Transactions in the Microservice world[1]
现在每个人(包含我)都在思考、构建微服务,分布式系统是微服务的外围准则和所有实现的上下文。
什么是分布式事务?
逾越网络上多个物理零碎或计算机的事务被简略的称为分布式事务。在微服务世界中,事务被宰割到多个服务中,须要按顺序调用这些服务以实现整个事务。
上面是一个单体电子商务系统应用事务的例子:
在下面的零碎中,如果用户向平台发送Checkout申请,平台将创立一个本地数据库事务,该事务操作多个数据库表,以解决订单并从库存中保留商品。如果有任何步骤失败,事务(包含订单和保留的商品)能够回滚。这被称为ACID(原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability),由数据库系统保障。
上面是电子商务系统合成为微服务的状况:
当咱们解耦这个零碎时,创立了微服务OrderMicroservice
和InventoryMicroservice
,各自有独立的数据库。当用户发动Checkout申请时,这两个微服务都将被调用从而将更改利用到各自的数据库中。因为事务是通过多个零碎跨多个数据库的,所以当初这是一个分布式事务。
微服务中的分布式事务有什么问题?
随着微服务体系架构的呈现,事务能够逾越多个微服务,从而逾越数据库,因而咱们当初无奈利用数据库的ACID个性,从而面临以下关键问题:
如何放弃事务的原子性?
原子性意味着事务要么实现所有步骤,要么没有实现任何步骤。在下面的例子中,如果InventoryMicroservice
办法中的“保留商品”失败,如何回滚OrderMicroservice
利用的“解决订单”?
如何解决并发申请?
如果某个微服务的对象被长久化到数据库中,同时有另一个申请读取雷同的对象。服务应该返回旧数据还是新数据?在下面的例子中,一旦OrderMicroservice
曾经实现,那么InventoryMicroservice
在执行更新的过程时,客户下单的申请中应该包含以后的订单吗?
现在,零碎应该为失败而设计,其中次要的问题就是解决分布式事务。上面援用Pat Helland的话:
一般来说,应用程序开发人员不会简略的就能实现反对分布式事务的大型可伸缩利用零碎。—— Pat Helland
可能的解决方案
在设计和构建基于微服务的利用时,上述两个问题十分要害。为了解决这些问题,上面列举几种办法:
- 两阶段提交(Two-Phase Commit)
- 最终一致性和弥补(Eventual Consistency and Compensation )/ SAGA
1. 两阶段提交
顾名思义,这种处理事务的形式有两个阶段,筹备阶段和提交阶段,其中起到重要作用的是事务协调器(Transaction Coordinator),负责保护事务的生命周期。
工作形式:
在筹备阶段,所有波及到的微服务都筹备提交,并告诉协调器曾经筹备好实现事务。而后在提交阶段,事务协调器向所有微服务收回提交或回滚命令。
以电子商务系统为例:
在下面的示例中(图3),当用户发送Checkout申请时,TransactionCoordinator
将发动一个带有所有上下文信息的全局事务。首先,向OrderMicroservice
发送prepare命令创立订单。而后,向InventoryMicroservice
发送prepare命令保留商品。当两个服务都能够执行更改时,它们将锁定对象,不再承受其余更改,并告诉TransactionCoordinator
。一旦TransactionCoordinator
确认所有微服务都已筹备好利用更改,就会通过申请事务commit来要求这些微服务长久化所作的更改,而后所有对象能力被解锁。
在失败的场景中(图4)——如果在任何时候有某个微服务没有做好筹备,TransactionCoordinator
将停止事务并发动回滚流程。图中因为某种原因,OrderMicroservice
未能创立订单,然而InventoryMicroservice
曾经回复说它筹备创立订单。TransactionCoordinator
将申请InventoryMicroservice
停止创立订单,并回滚所做的任何更改、解锁数据库对象。
长处
- 该办法保障事务是原子的。交易完结时,要么所有微服务都胜利,要么所有微服务都没有扭转。
- 其次,容许读写拆散,在事务协调器提交更改之前,对象上的更改是不可见的。
- 这种办法通过同步调用告诉客户端胜利或失败。
毛病
- 没什么事件是完满的,两阶段提交与单个微服务的解决工夫比起来慢很多,并且高度依赖于事务协调器,在高负载期间,事务协调器的确会升高零碎的速度。
- 另一个次要毛病是数据库行锁定,该锁可能成为性能瓶颈,并且可能呈现两个事务互相锁定造成的死锁。
2. 最终一致性和弥补/SAGA
最终一致性的最佳定义之一是microservices.io[2]形容的:每个服务在更新数据时公布一个事件。其余服务订阅事件,当接管到事件时,更新其数据。
在这种办法中,分布式事务由相干微服务上的异步本地事务来实现,微服务通过事件总线互相通信。
工作形式:
再以电子商务系统为例:
在下面的例子中(图5),客户端申请零碎解决订单。在处理过程中,Choreographer
收回一个Create Order事件,示意开始一个事务。OrderMicroservice
监听到这个事件并创立一个订单,如果胜利,收回一个Order Created事件。Choreographer
侦听此事件,并通过收回Reserve items事件持续保留商品。InventoryMicroservice
侦听此事件并保留商品,如果胜利,收回Items Reserved事件。在这个例子中,这意味着事务的完结。
微服务之间所有基于事件的通信都是通过事件总线进行的,并由另一个零碎编排以解决复杂性问题。
如果因为任何起因InventoryMicroservice
未能保留商品(图6),它会收回Failed to Reserve Items事件。Choreographer
侦听此事件,并通过收回Delete Order事件启动弥补事务。OrderMicroservice
侦听此事件并删除所创立的订单。
长处
这种办法的一大长处是每个微服务只关注本人的原子事务。如果某个服务破费了更长的工夫,其余微服务不会被阻塞,这也意味着不须要数据库锁。因为其基于异步事件的解决方案,这种办法能够使零碎在高负载下具备高度的可伸缩性。
毛病
该办法的次要毛病是没有读取隔离。这意味着在下面的示例中,客户端能够看到已创立的订单,但在下一秒中,因为弥补事务,订单会被删除。此外,当微服务的数量减少时,调试和保护就变得更加艰难。
论断
首先尽量避免分布式事务,如果正在构建新利用,那么就从单体开始,如Martin Fowler在MonolithFirst[3]中所形容的那样:
更常见的办法是从单体开始,逐步剥离边缘的微服务。这种办法能够在微服务体系架构的外围留下一个微小的单体,大多数新的开发都产生在微服务中,而这个单体相对来说变动不大。— Martin Fowler
当一个事件须要在两个中央更新数据时,与两阶段提交相比,最终一致性/SAGA计划是解决分布式事务的更好的形式,次要起因是两阶段提交在分布式环境中不能伸缩。不过最终一致性计划引入了新问题,例如如何以原子形式更新数据库和收回事件,因而采纳这种计划须要开发和测试团队扭转思维形式。
References: \
[1] Handling Distributed Transactions in the Microservice world: https://medium.com/swlh/handling-transactions-in-the-microser... \
[2] Event Driven Architecture: https://microservices.io/patterns/data/event-driven-architect... \
[3] MonolithFirst: https://martinfowler.com/bliki/MonolithFirst.html你好,我是俞凡,在Motorola做过研发,当初在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。 \
微信公众号:DeepNoMind
本文由mdnice多平台公布