在做玩法钱包项目时,其中一个场景,在使用账户资金创建红包时,先要预先判断账户资金是否充足,如果充足则冻结这笔资金,再创建红包,如果创建红包失败要取消冻结资金。这就涉及到了两个应用中红包库和资金库的跨应用跨库事务的问题。
本打算采取处理失败后使用metaq消息重试机制来做补偿,保证最终一致性。这种方式的好处是不引用新的中间件,对系统性能和吞吐量影响不大。但缺点也很明显,为了对数据进行补偿,针对不同场景需要新建不同的metaq重试任务,各种补丁代码也让代码逻辑变得复杂,且极端情况需要人工补偿。
针对以上问题去调研了集团的分布式事务中间件TXC,以下是对分布式事务及TXC的一些总结。
第一阶段是表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚。
两阶段提交方案应用非常广泛,它可以满足ACID,几乎所有收费的商业数据库都支持XA协议。
两阶段提交的致命缺点:XA在准备阶段不会提交本地事务,并且锁定资源时间长,在第一阶段上锁,第二阶段才能解锁,期间其他节点处于阻塞状态,这个对性能影响非常大,基本不适合应用于互联网海量流量的场景中。
TCC方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel三个操作。Try部分完成业务的准备工作,confirm部分完成业务的提交,cancel部分完成事务的回滚。基本原理如下图所示。
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
TCC方案让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。TCC采用最终一致性的方案,让资源不再需要长时间上锁,极大的提高了吞吐量,但牺牲了ACID中的C和I。
当然TCC方案也有不足之处,表现在以下两个方面:
基于消息的最终一致性方案是通过消息中间件保证上、下游应用数据操作的。基本思路是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。
消息方案从本质上讲是将分布式事务转换为多个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案性能比XA好很多,但同样对应用侵入性很高,应用需要进行大量业务改造,成本较高。
TXC(Taobao Transaction Constructor)是一个分布式事务中间件,它可以通过极少的代码侵入,实现分布式事务。
TXC的几个特性:
相对XA,TXC的性能会强很多,基本能满足互联网海量流量的场景
TXC支持同城容灾与两地三中心容灾,可以保证各种异常情况下的数据一致。
TXC采用了最终一致性,因此部分牺牲了一致性和隔离性。在此前提下,TXC在性能、隔离性之间提供了数个平衡点,应用程序管理员可以通过配置不同的TXC策略来选择本应用所需要的平衡点。
TXC对业务低侵入,业务代码最少只需要添加一行注解(@TxcTransaction)声明事务即可。业务与事务分离,将微服务从事务中解放出来,微服务关注于业务本身,不再需要考虑反向接口、幂等、回滚策略等复杂问题,极大降低了微服务开发的难度与工作量。
TXC的事务可以在应用之间随着HSF服务调用被传播,使得跨多应用、跨多库的数据库操作可以被包含在同一个事务内。
TXC由三个组件组成:客户端(TXC-Client),资源管理器(RM),事务协调器(TXC-Server)。客户端与事务协调器间,资源管理器与事务协调器间都是通过TXC分布式事务协议进行通信。客户端负责界定事务边界,开启/提交/回滚全局事务,资源管理器负责管理资源,支持的资源包括:TDDL/DRDS,Oracle,MySQL,RDS,PgSQL,H2,MQ,MetaQ,Notify。事务协调器,也就是TXC服务器,负责协调整个事务过程,是分布式事务处理的大脑。
事务过程:
在AT方式下,事务范围内所有分支的写SQL操作应该要么都执行,要么都不执行。
所有分支的状态都会被持久化,整个全局事务的状态也需要被持久化,以便在TXC Server或者client出故障之后可以回滚事务。
TXC提供2种隔离配置策略:弱隔离性策略(缺省策略)、强隔离性策略。
向TXC开发同学申请TXC资源,需提供aone应用名及TDDL的appname
在 POM 中引入:
@Configuration
public class TxcConfig {
@Bean
public TxcTransactionScaner txcTransactionScaner() {
//mode: 1:AT 2:MT 3:AT&MT 4:RT 5:AT&RT 6:MT&RT 7:AT&MT&RT
TxcTransactionScaner ts = new TxcTransactionScaner("union-account", "union-account", 1);
return ts;
}
}
Copy
@TxcTransaction(appName = "createInstance", timeout = 6000)
public RightsInstanceCreateResult createRightsInstance(RightsInstanceCreateParam createParam) {
return createRightsInstance(createParam);
}
CREATE TABLE `txc_undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`xid` varchar(100) NOT NULL COMMENT '全局事务ID',
`branch_id` bigint NOT NULL COMMENT '分支事务ID',
`rollback_info` longblob NOT NULL COMMENT 'LOG',
`status` int NOT NULL COMMENT '状态',
`server` varchar(32) NOT NULL COMMENT '分支所在的 TXC server IP',
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
) DEFAULT CHARACTER SET=utf8 AUTO_INCREMENT=211225994 COMMENT='事务日志表';
@HSFProvider(serviceInterface = AssetAccountService.class, enableTXC = true)
public class AssetAccountServiceImpl implements AssetAccountService {
...
}
以这次玩法钱包为例,涉及两个应用:玩法应用、资产账户应用,每个应用分别有各自的数据库(TDDL),流程时序如下:
在集群针对热点数据做压测时,QPS到达10就提升不上去了,配合TXC同学在预发和线上环境进行调试和压测,现将遇到的问题以及解决方法总结如下:
TXC完美解决了我们项目中分布式事务的问题,整体来说,AT模式下接入成本还是比较低的,代码入侵少,改造容易。从性能上来说,接入后RT会少量增加,这也是必然的,处理事务的消耗,但这点牺牲可以完全接受,毕竟保证一致性才是第一位的。