目录
微服务架构下的分布式事务
场景分类
DTP模型
DTP模型的局限性
微服务架构下的分布式事务特性
微服务架构下分布式事务处理模型
TCC模型
可靠消息模型
业务补偿模型
模型总结对比
单体架构下的分布式事务是一个服务内访问多个数据源的分布式事务,可以采用传统分布式事务处理模型——DTP(Distributed Transaction Processing)模型来解决。
在微服务的架构下,可能会出现跨服务、跨资源的分布式事务。在解决这类分布式事务时,微服务追求系统的可用性和最终一致性而非数据的强一致性。针对不同的微服务分布式事务场景,介绍不同的分布式事务处理模型,包括可靠消息模型、业务补偿模型和TCC(Try-Confirm/Cancel)模型,并总结每种模型的处理流程和优缺点。
单体应用架构在规模较小的情况下可以很好地满足业务需求,但随着互联网技术的发展,系统规模的持续扩大,单体架构暴露的问题也越来越多。一方面代码量大,逻辑复杂,不利于维护,更新某一个小模块需要重启整个项目;另一方面,不利于项目的横向扩展和按需伸缩。微服务架构可以将不同模块独立开来,使得各个模块之间更加的松耦合。各个模块可以独立部署、运行与更新,可以更加灵活的扩展与维护。此外,微服务与云计算天然契合,使得微服务架构被广泛讨论与采用。
随着微服务概念的兴起,如何在微服务架构下实施分布式事务是一个值得探讨的问题。早期事务的概念一般局限于资源层面,不管是单机事务还是分布式事务,都是交给资源层去做。然而在业务开发阶段,事务的概念上升到应用层。
应用层的事务场景有三种,如下图所示(AP表示应用程序,RS表示资源)。单体架构下的事务包含图1中的(a)和(b)两种,即单服务内访问单个数据资源的本地事务和单服务内访问多个数据资源的分布式事务;微服务架构下的分布式事务除了这两种类型外还会存在更复杂的情况,即图1的(c)所描述的跨服务、跨资源的分布式事务。
解决应用层的分布式事务,较为经典的方案是DTP模型。DTP模型延续了人们对以往分布式事务的理解,将业务层的分布式事务交由资源层处理,应用层不需要关注事务的执行流程。DTP模型只能解决图中单服务、跨资源场景,若要解决跨服务、跨资源的分布式事务,比较常见的方案有TCC模型、可靠消息模型和业务补偿模型。
传统单体架构下的分布式事务处理一般采用DTP模型。DTP模型由X/Open公司提出,也称 X/Open XA协议。
应用程序(AP)通常是指单个的应用,在一个服务内访问一到多个资源。资源管理器负责管理一到多个资源域,每个资源管理器实例(RM)管理一个资源域,负责具体的资源操作。事务管理器通过与资源管理器交流来协调全局事务。
首先,AP通过TX接口向TM注册一个全局事务并告知TM需要操作哪些资源域。然后TM通过XA接口通知每个管理对应资源域的RM开启一个子事务。接着AP通过RM对资源进行操作,并根据执行结果通知TM提交或回滚全局事务。如果所有子事务全部执行成功,则提交全局事务,否则回滚全局事务,即TM通知所有RM回滚子事务。
不难看出,DTP模型使用2PC(Two Phase Commit,两阶段提交)协议来保证分布式事务的原子性和一致性。TM充当全局事务协调者,RM充当全局事务参与者。2PC能够严格保证分布式事务的原子性和一致性,并且由于直接作用于资源层,对业务代码没有过多的侵入性,这使得DTP模型具有一定的普适性,满足大部分场景需求。
DTP模型的缺点在于性能低下,由于事务的隔离性,2PC一般采用基于锁的并发控制来控制对数据的访问,这意味着资源将被锁定直至事务结束。如果一个分布式事务对非热点数据的访问时间过长,将严重影响对于热点数据的访问,降低系统的并发性能。
此外,就分布式事务应用场景而言,DTP模型只适用于单服务、跨资源场景,不能有效解决跨服务、跨资源场景。而在微服务架构下,跨服务、跨资源分布式事务往往更加常见。
ACID是传统数据库中事务的设计理念,目的是保证数据的正确性,避免出现脏读、幻读等错误。但是在分布式系统中,尤其在应用层面,最重要的是满足业务需求,而非追求绝对的系统特性。根据CAP原理,强一致性、可用性和分区容错性不能同时满足。基于CAP原理的BASE理论(基本可用(Basically Available)-软状态(Soft State)-最终一致性(Eventual Consistency))采取了和ACID完全不同的设计思想,BASE理论通过牺牲强一致性来换取高可用性,但可以通过合适的方法达到最终一致性,这符合现实生活中分布式领域的特点。在此基础上实施分布式事务,事务是在应用层执行的,不仅能够保证数据的最终一致性,也能获取很好的可用性。
在微服务架构下,跨服务、跨资源的分布式事务满足CAP原理,所以后面讨论的微服务架构下的分布式事务处理模型,都是在BASE理论下解决跨服务、跨资源分布式事务的处理模型。
在单体应用中,各个模块的调用是通过方法或函数来实现的。而在微服务架构下,服务之间的交互必须通过服务间通信来解决。常用的两种服务间通信机制:基于消息的异步通信和基于请求/响应的同步通信。不同的分布式事务场景可能涉及不同的服务间通信机制,因此需要不同的分布式事务处理模型来解决。
下面通过一个例子来描述现实生活中经常见到的几类分布式事务场景:
周末,Tom想去参加某明星在某地的演唱会,中午先去某家餐馆吃饭,在一个微服务架构平台通过订餐接口完成一次订餐操作;然后通过该平台的订票接口预订好下午的车票和晚上的演唱会门票。大致活动如图所示。
整个微服务架构平台共有8个服务(不包括UI):
整个过程包含三个场景,每个场景下的分布式事务对应于不同的解决方案。本文将以场景1为例介绍TCC模型,以场景2介绍可靠消息模型,以场景3介绍业务补偿模型。
TCC是Try-Confirm-Cancel的缩写,分别对应着三种操作:Try操作、Confirm操作和Cancel操作,在服务中分别以三种接口的形式存在。下图是TCC模型图和正确执行时的工作流。
Try操作对业务进行检查,比如检查数据库资源是否充足,然后在业务层隔离业务活动需要的资源;
Confirm操作的前提是Try操作成功。这一步才是真正的执行业务操作,不需要检查资源情况,使用的就是Try操作预留的资源;
Cancel操作的前提是Try操作失败,释放Try操作预留的资源。
TCC模型大致分为三个部分:主服务、从服务和全局事务管理器。服务与服务之间通过请求/响应的同步通信机制进行交互。主服务提供对外接口,接受客户端请求,发起一个全局的业务活动并编排所有的事务参与者。从服务是全局事务的参与者,提供Try、Confirm和Cancel三个接口,通过调用这些接口来使从服务完成分支事务。全局事务管理器是整个分布式事务的协调者,记录全局事务的执行日志和事务状态,并且在Try阶段完成后,根据结果成功与否调用从服务的Confirm接口或Cancel接口。全局事务管理器是一个单独的服务。
TCC模型的执行流程,如下图所示。
这里需要注意两个问题:
TCC模型也是通过两阶段提交协议来保证分布式事务的原子性。在隔离性方面,DTP模型采用2PL[(两阶段封锁协议)来控制并发。与DTP模型不同的是,TCC是在业务层上锁。Try阶段完成后,隔离本次所需资源,释放掉数据库层面的锁,其他事务可继续以同种方式操作数据库的余下可用资源,这样就减少上锁时间,提高并发性。一致性方面,TCC模型允许短时间内的不一致。比如Tom在付款后商家可能过一会才收到消费金额,但这在一定程度上可以容忍的。
TCC模型适合于当前的各种微服务框架,业务层的编码可以灵活控制事务。但也正因为如此,TCC模型需要为正常的业务逻辑添加事务属性来满足分布式事务的处理要求,代码侵入性大,可移植性低,开发成本高。
可靠消息模型的最大特点在于借用了消息中间件来完成分布式事务,天然具备最终一致性的思想。下图是可靠消息模型图和正确执行时的工作流。
图中,主服务不会直接与从服务交互,中间通过可靠消息服务解耦。从服务不影响主服务的事务处理,只是被动地接受主服务事务处理的结果。服务与服务之间通过消息的发布与订阅进行异步通信。
图中,①到⑥步是主服务向可靠消息服务发送消息阶段(生产阶段):
只有①到⑥步顺利执行,即第⑥步成功将消息状态修改为“待消费”,才会执行⑦到⑩步。⑦到⑩步是可靠消息服务向从服务投递消息阶段(消费阶段):
可靠消息模型中,从服务的处理不会影响主服务,分布式事务的一致性体现在如果主服务成功执行,一定要向从服务成功地投递消息,从服务接收到消息后一定准确地完成自身的业务逻辑。结合场景2,一旦交易确定,Tom已完成支付操作,那么他一定要收到支付凭证服务给他发送的支付凭证账单。如果Tom由于网络原因没有付款成功,则一定不能收到支付凭证,但支付凭证服务对Tom的付款操作没有影响,是一个被动的操作。
可靠消息模型中分布式事务的原子性可以细化为两阶段:
然而,常见的中间件一般只负责接收消息和投递消息,不能正确地完成第一阶段(生产阶段)。因为不论是主服务先完成业务而后向中间件发送消息,还是先向中间件发送消息而后执行业务,都可能存在异常导致两边信息不一致。所以需要对常规的中间件进行包装,添加一个额外的服务完成图7中的①到⑥步。其中①到③步必不可少,有了这三步才能够保证在各种异常情况下主服务的业务执行结果和可靠消息的消息持久化过程保持一致。第二阶段(消费阶段)大部分的中间件可以实现,异常情况下中间件可能会向从服务多次投递消息。因此,从服务的接口设计需要满足幂等性。若多次投递无效,说明从服务有问题,必要时需要人工介入。
可靠消息模型是单独做一个消息服务来管理消息,这样可以做到独立部署和维护,重用性高,降低业务逻辑和消息管理的耦合性。也可以将这个任务直接交给主服务来做,但是这样做的弊端就是代码入侵,将消息的可靠性保证与正常的业务逻辑混合在一起,可移植性低,每一个类似的场景都需要根据业务重新编码。
相对于TCC模型,业务补偿模型的思想比较简单。一旦分布式事务某一步出错,可以利用回滚的思想将已经执行过的业务按照相反的逻辑执行一遍。业务补偿模型的架构和TCC模型架构类似,需要主从服务的共同参与。从服务的操作结果也影响主服务,也需要一个全局事务管理器。服务与服务之间通过请求/响应的同步通信机制进行交互。下图是业务补偿模型图
下图是正确执行时的工作流。
业务补偿模型中从服务的接口只有两个:Execute接口和Compensate接口。不同于TCC模型,补偿机制事务处理只有一个阶段,即Execute阶段,所有的业务活动都在这一阶段完成。分布式事务的正常流程是由主服务调度的,只有在异常情况下才会通过事务管理器进行补偿操作。
结合例子,Tom通过订票服务订票时是希望两张票能同时预定成功的,只买到门票或只买到车票该行程便无法成行。结合场景3可以描述业务补偿的执行过程如下:
业务补偿模式是通过事务管理器记录分布式事务的处理流程的,记录的信息越详细,越有利于控制业务补偿的范围。需要注意的是,业务补偿模型中,从服务事务的执行并不是一定要按顺序执行的。如果服务之间存在依赖关系就按照顺序执行(但这违反了微服务的设计理念,服务之间耦合性太大)。如果服务之间不存在依赖(如场景3中预定门票和预定火车票是不存在依赖关系的),服务内的子事务可以按照顺序执行,也可以并发执行。顺序执行会影响效率,但是一旦出错,只需要对已经执行过的业务进行补偿就行。并发执行可以提高效率,但是一旦出错,需要对所有服务进行业务补偿,代价较高。
与TCC模型相比,业务补偿模型容易理解,并且代码入侵少,不需要改造正常的业务逻辑,只需要添加一个补偿逻辑即可。但也正因为如此,会有一定的代码冗余,所以业务补偿模型适合于业务逻辑简单的场景。如果业务逻辑繁杂,代码量大,补偿逻辑的实现将变得很复杂。另外一旦业务的正向逻辑发生修改,补偿逻辑也需要做修改,将变得难以维护。就场景3而言,车票服务和演唱会服务只需要提供两个接口:订票和退票。正向流程调用订票接口,补偿流程调用退票接口。
业务补偿模型存在较为明显的两个缺点:
早期的事务是在资源层处理的,涉及到分布式事务时一般采用两阶段提交协议或者三阶段提交协议来解决。然而在微服务的架构下,事务的概念已经从资源层上升到应用层,事务的处理变得复杂多变,如果采用2PC或3PC来解决,将极大影响系统的性能。在分布式的环境中,不同的场景就要采取不同的分布式事务处理模型。通过下表可以清楚地看到各个模型的特性。