1 背景
分布式事务实现的方案,大体分为三种。第一种是使用控制资源管理层,实现事务两阶段提交两阶段事务(全局事务)。第二种是基于TCC的在业务层,利用反交易实现分布式事务。第三种是蚂蚁金服自己研发的收费的中间件DTX。
全局事务,是反伸缩设计,不适合在分布式系统应用。
TCC能在业务层,实现事务最终一致性,缺点是增加开发工作量。
DTX分析执行的sql,生成提交和回滚,解决了易用性问题,被誉为蚂蚁金服的黑科技。
结合平台技术特点,在开源TCC框架上加以改造,更加平台特殊要求做订制。增加如短信提醒、增加可靠性测试、事务开关等一系列设计,这是一条不错的思路。缺点是TCC增加开发的复杂度和工作量。所以尽量减少开发复杂度将是实现的改造目标之一。平台应评估自身需要,有选择地对重要资金交易、核心交易做改造。
本文档将详细介绍tcc-transaction的原理以及改造方案。
2 理解什么是TCC
理解TCC 执行原理才能合理的编码,合理的运用框架。否则会出现未知的情况。
TCC 是分布式事务解决方案之一,TCC分为三个模块。
Try: 尝试执行业务(冻结)
完成所有业务检查(一致性)
预留必须业务资源(准隔离性)
Confirm:确认执行业务(扣款)
真正执行业务
不作任何业务检查
只使用Try阶段预留的业务资源
Confirm操作满足幂等性
Cancel: 取消执行业务(解冻)
释放Try阶段预留的业务资源
Cancel操作满足幂等性
1、 Try层要做好充分的业务检查,如果Try成功,则调用Confirm确认;Try层失败,会调用Cancel进行撤销。
2、 Confirm和Cancel要满足幂等性,原因是由于网络延迟或者定时任务发起并发,有可能调用多次。
3、 Confirm和Cancel要加锁控制,乐观锁/悲观锁。
4、 对于正业务流程执行Try + Confirm,对于反业务流程执行Try + Cancel。
3 tcc-transaction
3.1 组件分层
tcc-transaction 主要包括以下模块:
事务拦截器、事务管理器、事务存储器、事务恢复job,持久化数据包括参与者与事务。
事务拦截器:
包括可补偿拦截器与资源拦截器。
可补偿拦截器,compensableTransactionInterceptor,是核心的事务拦截器,主要控制事务什么状态,该调用哪个层(Try、Confirm、Cancel)。
资源拦截器,resourceCoordinatorInterceptor,资源在这里指的是事务,在本拦截器会封装成必要的事务对象,并持久化,供可补偿拦截器调度。
事务管理器:
可补偿拦截器和资源拦截器配合组成。
事务存储器:
事务存储器负责把事务存储持久化,主要包括新建事务,更新事务,与查找需要恢复的事务。事务存储器目前有多种实现,包括jdbc、redis、zookeeper、文件系统。
事务恢复Job:
事务恢复定时任务是一种补偿,当出现事务不一致,Confirm或者Cancel失败会根据事务的状态,重新发起调用,达到最大次数限制则不再重新发起调用。这个地方可能以后需要增加预警设计。是基于quarter 内存事务器发起定时任务。
事务与事务参与者:
事务对象通过事务上下文传播,通过事务存储器存储。一个事务对象,记录每个事务参与者。
主事务(发起者)与分支事务(参与者):
发起者的事务也称为主事务,传播到分支事务(参与者),主事务控制事务整体提交和回滚。
3.2 源码模块
主要分为五个模块:tcc-transaction-core、tcc-transaction-api、tcc-transaction-dubbo、tcc-transaction-spring与tcc-transaction-nutz。
tcc-transaction-core:
负责核心代码事务,事务拦截器、事务存储器、事务序列化、事务持久化等
tcc-transaction-api:
基础类
tcc-transaction-dubbo:
dubbo上下文隐参支持
tcc-transaction-spring:
spring aop、定时任务、jdbc持久化支持
tcc-transaction-nutz:
spring aop 替代方案为nutz拦截器
定时任务(quarter) 替代方案为 nutz quartz
nutz 启动监听器,初始化必要配置
3.3 tcc-transaction-nutz
为了整合和适配nutz容器,nutz 容器增加了以下的改造和适配。
原实现 改造实现
spring quartz nutz-integration-quartz
spring aop nutz 拦截器
spring事务拦截器 nutz事务拦截器
dubbo 动态代理 使用jdk动态代理,增加nutz拦截器
spring bean配置,注解 nutz bean配置,注解
主要改造工程 tcc-transaction-spring 改为 tcc-transaction-nutz
以及事务拦截器改造等
3.4 tcc-transaction-dubbo-sample
测试demo由三个系统组成 订单系统、账户余额系统、红包系统。
上图需要注意两个点:
1、 confirm 和 cancel 并不需要发布dubbo服务,调用的入口都是从try方法进去,在客户端会生成一个动态的代理类,由事务拦截器决定调用try、confirm还是cancel。
2、 try、confirm和cancel都应该是一个单独的事务
3.4 管理台
tcc-transaction-server 是一个管理台项目,可以查看未执行一致的事务,目前功能比较简单。
如有需要将来可以扩展。
3.5 易用性(开发工作量)
易用性包括开发增加工作量和测试工作量。
主要包含:功能按模块拆分、编写部分反交易、幂等性、增加测试场景。
功能按模块拆分:
指的是要捋清try、confirm和cancel的逻辑,并编写在合适的模块。
编写部分反交易:
增加cancel部分的开发。
幂等性:
主要通过锁和状态来控制,锁可以使用悲观锁和乐观锁。状态,可能包含draft、confirming、canceling、confirmed、canceled。
测试场景:
增加try、confirm、cancel失败场景测试
4 开发示例
4.1 spring 容器
4.1.1 pom文件
引入maven依赖:
org.mengyun
tcc-transaction-spring
project.versionorg.mengyuntcc−transaction−dubbo
p
r
o
j
e
c
t
.
v
e
r
s
i
o
n
o
r
g
.
m
e
n
g
y
u
n
t
c
c
−
t
r
a
n
s
a
c
t
i
o
n
−
d
u
b
b
o
{project.version}
4.1.2 配置文件
4.1.2.1、web.xml
启动应用时,需要将tcc-transaction-spring jar中的tcc-transaction.xml加入到classpath中。如在web.xml中配置:
contextConfigLocation
classpath:tcc-transaction.xml,classpath:tcc-transaction-dubbo.xml
4.1.2.2、选择事务存储器
需要为参与事务的应用项目配置一个TransactionRepository,tcc-transaction框架使用transactionRepository持久化事务日志。可以选择FileSystemTransactionRepository、SpringJdbcTransactionRepository、RedisTransactionRepository或ZooKeeperTransactionRepository。
使用RedisTransactionRepository配置示例如下:
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="1000"/>
<property name="maxWaitMillis" value="1000"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" ref="jedisPoolConfig"/>
<constructor-arg index="1" value="127.0.0.1"/>
<constructor-arg index="2" value="6379" type="int"/>
<constructor-arg index="3" value="1000" type="int"/>
<constructor-arg index="4" type="java.lang.String">
<null/>
</constructor-arg>
<constructor-arg index="5" value="0" type="int"/>
</bean>
4.1.2.3、设置恢复策略(可选)
当Tcc事务异常后,恢复Job将会定期恢复事务。在Spring配置文件中配置RecoverConfig类型的Bean来设置恢复策略示例:
com.alibaba.dubbo.remoting.TimeoutException
其中maxRetryCount表示一个事务最多尝试恢复次数,超过将不再自动恢复,需要人工干预,默认是30次。
recoverDuration表示一个事务日志当超过一定时间间隔后没有更新就会被认为是发生了异常,需要恢复,恢复Job将扫描超过这个时间间隔依旧没有更新的事务日志,并对这些事务进行恢复,时间单位是秒,默认是120秒。
cronExpression表示恢复Job触发间隔配置,默认是0 /1 * * ?。
delayCancelExceptions(1.2.3版中新加的配置)表示系统发生了设置的异常时,主事务不立即rollback,而是由恢复job来执行事务恢复。通常需要将超时异常设置为delayCancelExceptions,这样可以避免因为服务调用时发生了超时异常,主事务如果立刻rollback, 但是从事务还没执行完,从而造成主事务rollback失败。示例中com.alibaba.dubbo.remoting.TimeoutException为底层rpc框架为dubbo,超时异常发生时框架抛出的超时异常类,需要将其加入delayCancelExceptions中。
4.1.3 编码
4.1.3.1 事务发起者
try方法:
@Compensable(confirmMethod = “confirmMakePayment”, cancelMethod = “cancelMakePayment”)
public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
System.out.println(“order try make payment called.time seq:” + DateFormatUtils.format(Calendar.getInstance(), “yyyy-MM-dd HH:mm:ss”));
order.pay(redPacketPayAmount, capitalPayAmount);
orderRepository.updateOrder(order);
String result = capitalTradeOrderService.record(buildCapitalTradeOrderDto(order));
String result2 = redPacketTradeOrderService.record(buildRedPacketTradeOrderDto(order));
}
confirm方法:
public void confirmMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
// TODO SOMETHING
}
cancel方法:
public void cancelMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
// TODO SOMETHING
}
4.1.3.2 分支事务
Dubbo 接口
@Compensable
public String record(CapitalTradeOrderDto tradeOrderDto);
try实现方法:
@Compensable(confirmMethod = “confirmRecord”, cancelMethod = “cancelRecord”, transactionContextEditor = DubboTransactionContextEditor.class)
public String record(CapitalTradeOrderDto tradeOrderDto) {
confirm方法:
public void confirmRecord(CapitalTradeOrderDto tradeOrderDto) {
cancel方法:
public void cancelRecord(CapitalTradeOrderDto tradeOrderDto) {
4.2 nutz 容器
4.2.1 pom文件
引入maven依赖:
org.mengyun
tcc-transaction-nutz
project.versionorg.mengyuntcc−transaction−dubbo
p
r
o
j
e
c
t
.
v
e
r
s
i
o
n
o
r
g
.
m
e
n
g
y
u
n
t
c
c
−
t
r
a
n
s
a
c
t
i
o
n
−
d
u
b
b
o
{project.version}
org.nutz
nutz-integration-quartz
1.r.66
4.2.2 配置文件
4.2.2.1 MainModule
增加注解扫描:org.mengyun与*org.nutz.integration.quartz.QuartzIocLoader
@IocBy(type=ComboIocProvider.class,args = {“*js”, “ioc/”,
“*annotation”,”org.mengyun”,
“*org.nutz.integration.quartz.QuartzIocLoader”})
4.2.2.2 增加quartz.properties
org.quartz.scheduler.instanceName = NutzbookScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.scheduler.skipUpdateCheck=true
4.2.2.3 nutz 拦截器 修改
主要引入ComboAopConfigration拦截器,增加对注解和json两种
$aop : {
type : ‘org.nutz.ioc.aop.config.impl.ComboAopConfigration’,
fields : {
aopConfigrations : [
{
type : ‘org.nutz.ioc.aop.config.impl.AnnotationAopConfigration’
},
{
type : ‘org.nutz.ioc.aop.config.impl.JsonAopConfigration’,
fields : {
itemList : [ [‘org\.mengyun\.tcctransaction\.sample\.dubbo\.capital\.service\..+’,’.+’,’ioc:txREAD_COMMITTED’]
]
}
}
]
}
},
4.2.2.4新增tcc.json
var ioc = {
conf : {
type : “org.nutz.ioc.impl.PropertiesProxy”,
fields : {
paths : [“custom/”]
}
},
jedisPoolConfig : {
type : “redis.clients.jedis.JedisPoolConfig”,
fields : {
maxTotal : “1000”,
maxWaitMillis : “1000”,
}
},
jedisPool : {
type : “redis.clients.jedis.JedisPool”,
args : [
{refer: ‘jedisPoolConfig’},”127.0.0.1”,”6379”,”1000”
]
},
transactionRepository : {
type : “org.mengyun.tcctransaction.repository.RedisTransactionRepository”,
fields : {
keyPrefix : “TCC:CAP:”,
jedisPool : {refer: ‘jedisPool’}
}
},
/*recoverConfig : {
type : “org.mengyun.tcctransaction.spring.recover.DefaultRecoverConfig”,
fields : {
maxRetryCount : “30”,
recoverDuration : “60”,
recoverDuration : “0/30 * * * * ?”,
delayCancelExceptions : [
‘com.alibaba.dubbo.remoting.TimeoutException’,
‘org.mengyun.tcctransaction.OptimisticLockException’,
‘java.net.SocketTimeoutException’
]
}
},*/
transactionManager : {
type : “org.mengyun.tcctransaction.TransactionManager”,
fields : {
transactionRepository : {refer: ‘transactionRepository’}
}
},
};
4.2.2.5 新增 custom/cron.properties
cron.org.mengyun.tcctransaction.spring.recover.RecoverScheduledJob=0 /1 * * ?
cron.pkgs=org.mengyun.tcctransaction.spring.recover
4.2.3 编码
参考spring容器实现,在有@Compensable方法实现上,增加注解
@Aop({“compensableTransactionForNutzInterceptor”,”resourceCoordinatorForNutzInterceptor”})
4.3 注意事项
4.3.1 幂等性
confirm 和cancel方法要满足幂等性,因为网络延迟和定时任务很可能会重发
4.3.2 注意维护confirm 和cancel
在修改try方法,要考虑cancel的影响
5 测试要求
正案例测试
反向案例测试
性能测试
场景测试
Try + Confirm + Cancel,失败可能测试
6 订制化改造
6.1 手工处理,预警等
6.2 管理台优化升级