在微服务架构下,我们最容易遇到的一个问题就是分布式事务处理问题,当你微服务模块拆分越细,那么遇到分布式事务处理的场景就越多。即使是同一个微服务模块,对应一个业务数据库,但是你某个业务逻辑的实现是调用两个Service接口服务来完成的,同样也是分布式事务问题。
因此有必要对分布式事务整体解决思路进行下总结。
分布式事务概述
图片
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
一个分布式事务包含一组操作序列,由两个或两个以上的网络主机参与。通常主机提供事务性资源,而事务管理器负责创建和管理全局事务,由事务管理器协调事务参与的资源。分布式事务和本地事务并无太大不同,也需保证事务的四个属性(原子性、一致性、隔离性、持久性)。
对于分布式事务的潜在场景,可以简单的分为三类:
1.跨资源
一个完整业务需要操作两个独立数据库,比如需要在A数据库插入一条订单记录,同时更新B数据库中的库存状态,两个操作跨数据库,但是需要控制在一个完整事务里面。
2.资源+服务组合
在和工作流集成场景中出现,业务用户在提交单据的时候需要后台执行两个操作,首先是调用本地API将单据数据保存到本地数据库,其次是调用启动流程WebService服务。
3.跨服务
由于Web Service服务本身无状态,即使是同一个数据库提供给处理的两个WebService服务,在进行调用和组合的时候也属于分布式事务控制。
对于分布式事务的主流解决方法,主要包括了XA两阶段提交,事务补偿和基于BASE的最终一致性(可靠消息传输),首先再对这三种方法做下简单描述。
强一致性-两阶段提交模型
图片
两阶段提交,因为它是实现XA分布式事务的关键(确切地说:两阶段提交主要保证了分布式事务的原子性:即所有节点要么全做要么全不做)。所谓的两个阶段是指:第一阶段:准备阶段和第二阶段:提交阶段。具体过程如下:
阶段一:开始向事务涉及到的全部资源发送提交前信息
此时,事务涉及到的资源还有最后一次机会来结束事务。如果任意一个资源决定异常结束事务,则整个事务取消,不会进行资源的更新。否则,事务将正常执行,除非发生灾难性的失败。为了防止会发生灾难性的失败,所有资源的更新都会写入到日志中。这些日志是永久性的,因此,这些日志会幸免于难并且在失败之后可以重新对所有资源进行更新。
阶段二:只在阶段一没有异常结束的时候才会发生
此时,所有能被定位和单独控制的资源管理器都将开始执行真正的数据更新。事务管理器将基于第一个阶段的投票结果进行决策:提交或取消。当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
从以上工作过程可以看出,两阶段提交协议正确工作的前提假设是每个节点都会记录来写前日志(write-ahead log)并持久性存储,即使节点发生故障日志也不会丢失。同时假设节点不会发生永久性故障而且任意两个节点都可以互相通信。
两阶段提交优缺点分析
两阶段提交协议最大的劣势是其通过阻塞完成的协议,在事务参与者等待消息的时候处于阻塞状态,事务参与者中其他进程则需要等待阻塞进程释放资源才能使用。如果事务管理器发生了故障,那么事务参与者将无法完成事务则一直等待下去。同时两阶段提交协议没有容错机制,一个节点发生故障整个事务都要回滚,代价比较大。
两阶段提交协议仅在一种情况下可能导致数据不一致,所有或部分参与者都响应了Prepare阶段,在任何参与者收到事务管理器提交或回滚决定之前事务管理器宕机死亡,此时所有参与者都必须等待事务管理器恢复。而可能事务管理器此时已经丢失了事务上下文,这时需人工介入参与数据恢复。
两阶段提交协议仅仅是提供了分布式事务中的原子性属性,保证一个事务在全局要么全部提交,要么全部回滚,但是在并发控制上(隔离性),仍然需要封锁协议,为了达到分布式环境下全局串行和避免一个事务的失败可能引起一连串事务的回滚,通常使用强两阶段封锁协议(SS2PL)。
两阶段封锁协议并不保证不会发生死锁,数据库系统必须采取其他的措施,预防和解决死锁问题。但常见的数据库系统通常都实现了隔离级别,不同的隔离级别对于不同的锁机制,下表列出他们的对应关系。值得一提的是,两阶段封锁协议并不保证不会发生死锁,数据库系统必须采取其他的措施,预防和解决死锁问题。所以说,如果两阶段提交协议中事务时间越长,那么锁等待的时间和死锁的概率也会变大。
弱一致性-事务补偿模型
图片
事务补偿机制是指在事务链中的任何一个正向事务操作,都必须存在一个完全符合回滚规则的可逆事务。例如存款操作通过取款操作来补偿、买入通过卖出来补偿。
工作方式和限制
这里的“补偿”与数据库事务中的“回滚”是有区别的,“回滚”是指操作的取消,“回滚”前后对外界来讲,数据是一致的。而“补偿”则是独立的逆向操作,如果事务执行了“补偿操作”,外界可能会看到数据的两种状态。从这个角度讲,“回滚”需要锁定资源。从数据库操作上来“补偿操作”其实也是一次短事务。而“回滚”是一个事务内的操作。
事务补偿通常在实现时采取嵌套事务的方式,即把一个主事务拆分成多个从业务操作,事务的发起和结束由主事务完成。从业务服务提供的业务操作提供补偿操作,补偿操作可以抵销(或部分抵销)正向业务操作的业务结果。业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动取消时调用补偿操作。具体回滚整个事务还是回退到某个事务点,可以依据具体业务来处理。
在上面方式中可以看到需要手工编写大量的代码来处理以保证事务的完整性,我们可以考虑实现一个通用的事务管理器,实现事务链和事务上下文的管理。对于事务链上的任何一个服务正向和逆向操作均在事务管理和协同器上注册,由事务管理器接管所有的事务补偿和回滚操作。
补偿机制能正确工作是基于事务是可以补偿这一前提,如果这一前提无法满足,那么就无法使用这一机制。现实业务场景中,确实存在这样的业务,例如证券交易,因为对时效性特别敏感,不能简单地使用卖出(买入)来补偿买入(卖出),因为不同时间交易的价格可能差距很大。
另外,补偿操作也是一次事务操作,考虑到补偿操作也是有可能失败的,所以,补偿操作应该支持重试,这就要求补偿操作满足幂等性。即重复调用多次产生的业务结果与调用一次产生的业务结果相同。
事务补偿场景举例
在前面已经强调了,对于事务补偿必须满足幂等性要求,而且不能对时效性太敏感。
场景:采购订单在提交时候调用预算检查和扣减,但是如果单据保存失败需要再次调用预算检查和调整,将扣减预算退回。
那么基于以上场景事务补偿的核心实现逻辑如下:
图片
- 正常情况,调用预算扣减服务后,保存采购订单,两者需要同时成功
- 如果调用采购订单保存服务出现失败,那么就需要对已经扣减的预算进行补偿和返还
即调用和扣减预算完全对等的一个逆向操作,将扣减服务造成的影响全部回退
以上即是对事务补偿机制的关键说明。
BASE最终一致性-可靠消息队列
CAP理论概述
图片
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的CAP原理包含如下三个元素。
- C:Consistency,一致性。在分布式系统中的所有数据 备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。
- A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
- P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。
CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。因此在进行分布式架构设计时,必须做出取舍。而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。
当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可。
从CAP理论到BASE理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的简写,由 eBay 架构师 Dan Pritchett 于 2008 年在《BASE: An Acid Alternative》论文中首次提出。
BASE 思想与 ACID 原理截然不同,它满足 CAP 原理,通过牺牲强一致性获得可用性, 一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。BASE 模型包含如下三个元素:
- BA:(Basically Available ),基本可用。
- S:( Soft State),软状态,状态可以在一段时间内不同步。
- E:(Eventually Consistent ),最终一致,在一定的时间窗口内, 最终达成一致即可。
BASE理论是基于CAP定理演化而来,是对CAP中一致性和可用性权衡的结果。核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。
1、基本可用:指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜索引擎0.5秒返回查询结果,但由于故障,2秒响应查询结果;网页访问过大时,部分用户提供降级服务,等。
2、软状态:软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点间副本同步的时候存在延时。
3、最终一致性:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。
BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID是传统数据库常用的概念设计,追求强一致性模型。
基于可靠消息队列实现最终一致性
图片
基于消息的最终一致性是消除了分布式事务,是一种在BASE思想指导下比较好的方案。这种方案实现了两个服务之间的解耦,解耦的关键就是异步消息和消息持久化机制。在两个服务调用之间,会存在一个真空期,这段时间相关数据不一致,而只是在一个事务的中间状态。
事务主动方的业务服务事务提交和消息发送之间必须通过事务同步,可以通过事务管理器进行管理,如两阶段提交。事务主动方在完成事务提交和消息发送之后,它的最终处理结果不再受到事务被动方的影响。即发送到事务被动方的事务要么成功,要么重试。
所以,消息同样需要满足幂等性。实际情况下,消息很难具有幂等性,解决这一问题的方法是使用另一个表记录已经被成功应用的消息,这样就可以通过避免消息多次被应用,从而达到幂等性。
在实际应用中,如果消息接收端的服务出现失败,可能需要人工干预。
场景举例说明
场景:在采购系统中拟制采购订单,在提交单据申请的时候既需要将单据成功保存到本地,同时又需要启动远程流程平台提供的流程启动服务。在该场景中,第二个步骤属于必须要最终完成的操作,同时业务上也允许最终一致(不要因为流程平台本身问题导致单据提交不成功,启动流程失败如何重试是系统内部的事情)
对于该场景,基于消息实现最终一致性逻辑如下:
图片
三种事务处理方案的比较和选择
对于上面谈到的三种事务处理方案,我们列个表格比较如下:
图片
当前主流的方法仍然是事务补偿和BASE最终一致性,同时可以看到,基于消息中间件实现的事务最终一致性由于本身具备高可靠,高性能,并满足大并发的高吞吐量,因此在互联网应用往往采用的更加多。在企业内部的基于SOA服务的分布式事务控制,
图片
场景:业务操作需要同时调用 ServiceA-》ServiceB 两个服务,并控制到一个分布式事务里面。
两种方法选择思路为:
If ( 服务A是否可以完整设计出 –A补偿服务)
{
//-A补偿服务能够保证A服务影响完整回退
//在调用-A前,A服务调用影响不会波及到后续操作
if(服务A成功 则 服务B调用必须成功
and B只要最终成功即可)
{
//在服务A成功的情况下服务B不存在因业务逻辑处理和校验导致的不成功。
Choose 最终一致性方案;(优选方案)
}
Else
{
Choose 事务补偿方案;
}
}
Else
{
Choose 最终一致性方案;
}
整体思路也就是说能够采用最终一致性的地方尽量采用最终一致性来解决问题。
对于不能采用BASE事务最终一致性的地方,特别是在ServiceB在调用的时候存在较多的业务逻辑校验,因此ServiceA即使调用成功也会经常出现由于逻辑校验导致ServiceB调用不成功,那么这种情况下就很难用BASE方案,否则就会导致大量的人工补偿操作和处理。
对于ServiceB的调用如果需求都是最终一致,同时ServiceB的调用不存在由于业务原因导致的调用失败,那么都建议采用BASE事务最终一致性方案。由于BASE方案基于消息中间件来实现,通过消息中间件既可以实现大并发下的吞吐量,同时也可以实现两个服务调用间的彻底解耦。