文章摘要:原来大型分布式/微服务系统中解决数据一致性问题,居然是通过……
一、为什么要使用分布式事务—2PC?
传统事务是使用数据库自身的事务属性(ACID),而数据库自身的事务属性是局限于当前实例,不能实现跨库。而对于大型分布式/微服务集群系统中,不仅存在着跨库的事务,还存在很多不同系统/服务之间的RPC调用,这种调用往往也需要保证业务以及数据的一致性。因此,有必要使用一种分布式事务框架来协调整个端到端业务调用链路的应用和数据库来保证业务最终的数据一致性,而目前在分布式事务中用的比较多的即为基于所有服务参与者投票的二阶段协议(2PC)。
二、分布式事务基础模型
由于在大型SOA/微服务系统架构下,一次业务请求往往会跨越多个服务,多个服务共同协调完成一次端到端的全链路业务调用时,由于业务约束或者服务提供者的故障等原因造成多个系统中数据不一致或通信延迟等问题,因此对于分布式/微服务系统中的多服务协调调用场景需要分布式事务来保证系统的ACID属性。
1.X/Open DTP模型
目前二阶段的分布式事务一般基于以下的X/Open
DTP(Distributed Transaction Processing Reference Model)。
其中,
AppService:是服务应用。可以理解为使用分布式事务的应用程序。
RM(Resource Manager):资源管理器。这里主要指的是服务应用通过资源管理器对数据库或者消息队列服务器这类资源进行控制,资源需要实现XA接口;
TM(Transaction Manager):事务管理器。负责协调和管理事务,提供给服务应用编程接口管理上面的资源管理器。
AppService可以和TM、RM进行通信,TM和RM也能够进行通信,服务应用可以通过TX接口向事务管理器发起事务、提交事务和回滚事务。TM和RM通过XA接口进行双向通信,比如TM通知RM提交事务或回滚事务;RM把提交结果通知给TM。AppService和RM之间则通过RM提供的本地API接口进行资源控制。
2.分布式二阶段提交协议
通常在一个全链路的端到端业务操作中,往往会跨多个节点、多个应用,为了能够保证全局事务的ACID特性,需要引入一个协调组件(这里为TM)来控制所有服务参与者(这里为RM)的操作结果,根据所有参与者的反馈结果来决定整个分布式事务究竟是提交还是回滚的结果。
第一阶段:称为准备(prepare)阶段。事务协调者向各个服务应用发送prepare请求,服务应用在得到请求后做预处理操作,预处理可能是做预检查,也可能是把请求临时存储,可以理解为是一种试探性地提交。下面是一般的步骤:
a.事务协调者会问所有的参与者服务,是否可以提交操作。
b.各个参与者开始事务执行的准备工作:如资源上锁,预留资源,写回滚/重试的log。
c.参与者响应协调者,如果事务准备工作成功,则回应“可以提交”,否则回应拒绝提交。
第二阶段:称为提交(commit)/回滚(rollback)阶段。是指事务真正提交或者回滚的阶段。如果事务协调者发现事务参与者有一个在prepare阶段出现失败,则会要求所有的参与者进行回滚。如果协调者发现所有的参与者都prepare操作都是成功,那么他将向所有的参与者发出提交请求,这时所有参与者才会正式提交。由此保证了要求全部提交成功,要么全部失败。下面是具体步骤:
a.如果所有的参与者都回应“可以提交”,那么协调者向所有参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各个服务的“完成”回应后结束事务。
b.如果有一个参与者回应“拒绝提交”,那么协调者向所有的参与者发送“回滚操作”,并释放所有的资源,然后回应“回滚完成”,协调者收集各个服务应用的“回滚”返回后,取消整体的分布式事务。
下图为二阶段的成功和失败示例图:
三、分布式事务—2PC的基本概念&&主要执行流程
下面先用一张框图来大致描述下分布式事务DTS,二阶段提交是如何执行的:
上面的图中是以订单支付来举例的。图中的业务核心这里主要指的订单系统(订单系统作为大型微服务系统中的一个服务本身并不具备支付的服务能力,因此需要调用其他或者第三方的账务计费系统完成支付以及结算业务),在这里它发起分布式事务,需要调用账务系统以完成订单支付业务活动。DTS协调者实际上即为DTS Server,它会统一协调参与分布式事务的各个参与者之间提交/回滚。
1.事务发起者与参与者
在本文的上篇中,只是将分布式系统中所有服务称之为参与者。由于二阶段提交的“准备”操作实现复杂性和效率都会在实际的业务中产生影响,因此需要引入一个简单的优化,在模型中将事务将有一个参与者不参与二段提交的过程(称为单阶段参与者),而是在其余二阶段参与者都准备好后,再请求单阶段参与者提交,单阶段参与者的提交结果将决定整个分布式事务的结果。如果单阶段参与者提交成功,那么协调者要求其余参与者都提交,如果提交失败则协调者要求其余参与者都事务回滚。这里的单阶段参与者也即为下文所述的事务发起方。
在二阶段的分布式事务中,事务发起者又称为发起方,它是整个业务活动的主体、是服务的编排者,由它启动业务活动并决定业务活动提交或回滚。简单的例子如上图中,业务核心可以是订单中心等,它需要调用账务计费服务完成支付功能,那么此时它是发起分布式事务活动的发起方,它命令其他系统协助完成一次完整的分布式事务。那么参与到这一次分布式事务中来的其他系统就称为参与者,这里账务计费服务就是参与者,它协助发起方完成相应的动作,执行具体的业务逻辑。
下面给出两个定义:
(1)Activity:把整个分布式事务称为一次主业务活动(Activity)
(2)Action::把参与者的一次方法调用称为原子活动(Action)
分布式事务必须确保各个角色的一致性和有效性,因此要保证框架进行提交或者回滚,Activity和Action,必须在分布式事务执行阶段将状态等信息记录下来,如果这些信息只记录在内存或者本地存储中,该中记录方式容错性低,无法应付宕机等问题,因此这些状态信息记录在DB中,通过建立分布式业务控制活动主表(business_activity)来记录全局事务的活动状态,以及原子业务活动表(business_action)来记录原子业务活动的状态。在开始一个分布式事务的时候框架先创建activity主活动记录,每调用一个参与者就会再创建一个action原子活动记录。
2.分布式事务中的同库/异库模式
这里可以根据业务控制活动主表记录(简称为activity)和原子业务活动表(简称为action)记录是否保存在业务库还是中DTS
Server库,分为以下两种情况:
(1)同库模式
发起方会在自己业务库的事务模板(transactionTemplate)中开起分布式事务,并且将上述的business_activity/business_action存储在业务库中,由于DB的插入操作相对较快且无需额外的RPC调用,因此适合业务量比较大的业务场景。但需要进行额外的编程配置,比如配置数据源、事务模板、DAO等。
(2)异库模式
发起方会在自己业务库的事务模板中开起分布式事务,而business_activity/business_activity这两个表会存储在DTS Server这个中间协调者的数据库内,即为这两个表和业务表不同库。该模式下,通常需要RPC调用DTS Server查询数据库的元数据。适用业务量较小的场景。但是分布式活动方便集中管理,管理方便易于排查问题。
同库模式和异库模式存在的原因是必须记录主事务的信息到DB中,方便提交/回滚以及recover(如果是同库模式,主要是recovery,如果是异库模式就是提交/回滚+recovery),确保各系统之间的一致性,但是由于分布式事务中必然存在一些外围系统接入而且根据业务情况无法保证数据表总是存放在本地库中,因此产生了该两种模式。
同样对于整个调用链路中的服务应用参与者来说也可以分为以下两种情况:
(1)local模式
事务元数据Action记录在本地和Activity同库,且没有直接remote参与者,发起方在本地对参与者发起提交/回滚。原子活动记录Action和Activity都是同库,速度比较快。但是,原子活动的context内容由框架获取预处理阶段提交的参数无法修改。
(2)remote模式
没有配置本地数据源,Activity和Action的记录都存在DTSServer中,整个回滚和提交过程将由中间协调者DTS Server来完成。该种模式的优点在于可以自行创建原子活动记录,能够控制context内容;可以启动嵌套的分布式事务。然而缺点也比较明显,创建原子活动记录需要手动触发,记录由远程服务存储,效率比较低。
由于在大型分布式/微服务应用中,实际生产环境中的业务量往往较大,综合考虑性能、稳定性等因素,因此通常会使用发起方同库+服务参与者local/remote模式。
3.发起方和参与者的具体执行流程
限于篇幅,下面分别通过两幅框图来说明发起方同库模式下,服务参与方存在local和remote模式的业务调用场景。
(1)一阶段:在订单中心(业务核心)这个分布式事务发起方开启事务模块,将Activity记录插入至本地业务库的分布式业务控制活动主表(business_activity)中,对参与者——账务计费系统发起事务时,如果参与者服务响应OK,则拦截器将自动将Action记录插入至本地的原子业务活动表(business_action)中。
(2)二阶段:在正常的情况下,发起方直接向参与者提交/回滚操作。
(1)一阶段:在该模式下,Activity记录在发起方本地,此时参与者服务是remote模式,需要参与者自己向DTS Server注册分支事务(即为在DTS Server的库中插入action记录)。
(2)二阶段:发起方事务先向DTS Server发送提交/回滚操作,然后DTS Server再向参与者发起提交/回滚操作。
4.分布式事务的异常处理流程
任何系统设计都需要考虑服务故障、服务器宕机、网络通信延迟以及数据库不可用等外部因素。上面只是对分布式事务正常的执行流程和原理进行了阐述,那如果在事务执行中,一阶段失败回滚的时候,恰好系统服务故障导致回滚不成功;或者二阶段提交时候,系统服务故障导致提交不成功,那么如何来保证一致性呢?在这里我们可以考虑在分布式事务协调者的DTS Server中设计一套异常恢复机制来对进行容错和恢复。
可以清楚看到,如果状态为U,表明有可能是异库模式下的主活动记录,这时并不清楚当前事务到底执行到何种状态,是否是确认提交或者确认回滚,那么需要对进行回查处理,需要我们自己来写回查代码,将回查的结果返回。如果是DONE,表明一阶段完成,此时对应的是确认提交状态,应为C;如果为NOT_DONE,那么表明一阶段失败,对应了确认回滚状态,应为I。然后再调用对应的二阶段的提交/回滚就即可。如果捞取出来状态为I/C,就直接进行回滚/提交。
DTS Server中有一个异常恢复的recover线程在运行,每隔1分钟就去Activity记录所在的DB捞取记录,如果这是状态被记录为异常状态,那么recover程序就会尝试恢复被中断的事务,也就是重试,直到二阶段事务成功提交/回滚事务。
作者:癫狂侠
链接:https://www.jianshu.com/p/130ac1f2ab51