TCC设计理解-方案设计(2)

TCC理解2之方案设计

TCC方案选型

如何在设计一个方案解决上面提出的问题,以及这个方案在权衡利弊上的考虑。

Try阶段:

尝试执行

  • 完成所有业务检查(一致性)
  • 预留必须业务资源(准隔离性)

Confirm阶段:

确认执行

  • 真正执行业务
  • 不作任何业务检查
  • 只使用Try阶段预留的业务资源
  • Confirm操作满足幂等性

要求具备幂等设计,Confirm阶段失败的原因可能存在3个地方

  1. Leader自身异常
  2. Service执行异常
  3. Service通讯异常

在设计时,无论异常在什么地方,这个Leader都在一个JDBC事务内,整个事务将一直重试,重试会在两个地方存在

  1. 请求阶段重试,默认3次
  2. 定时器重试,无限重试,是否有必要和请求阶段重试互斥设计还需要再研究

Cancel阶段:

取消执行

  • 释放Try阶段预留的业务资源
  • Cancel操作满足幂等性

Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。

事务设计

单个业务需要提供TCC三个接口,分别对应TCC事务的begin、commit、rollback

主要伪代码如下:

1
2
3
4
5
6
public interface TransactionManager {
Transaction begin();
void commit();
void rollback();
}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TCCTransactionManager implements TransactionManager {
public Transaction begin() {
...
}
public void commit() {
...
}
public void rollback() {
...
}
}

定义事务类

1
2
3
4
5
6
7
8
public class Transaction {
private Long xid;
private int status;
...
}

定义事务本地线程变量

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TransactionHolder {
private static final ThreadLocal<Deque<Transaction>> transaction = new ThreadLocal<>();
public static Transaction getCurrentTransaction(){
...
}
public static void put(Transaction action) {
...
}
}

业务伪代码设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountService() {
@TCCTransaction
public void create() {
...
}
public void confirm() {
...
}
public void cancel() {
...
}
}

动态代理调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
transactionManager.begin();
try {
AccountService service = Proxy..
service.create();
} cache (Exception e) {
transactionManager.rollback();
}
transactionManager.commit();

确认和回滚接口调用如下:

1
2
3
4
5
6
7
8
9
10
commit() {
AccountService service = Proxy..
service.confirm();
}
rollback() {
AccountService service = Proxy..
service.cancel();
}

案例分析

前提:

  • 需要满足幂等性。

    • INSERT语句要求必须包含主键,且不能是自增主键。
    • UPDATE语句要求幂等,不能是UPDATE xxx SET x=x+1
    • DELETE语句无要求

微服务:

  • 业务逻辑需要按照分布式拆分,执行过程按照强阶段拆分

    • 订单服务
    • 红包服务
    • 资金服务

过程介绍:

使用红包帐户和资金帐户来付款,红包帐户服务和资金帐户服务在不同的系统中。示例中,有两个SOA提供方,一个是CapitalTradeOrderService,代表着资金帐户服务,另一个是RedPacketTradeOrderService,代表着红包帐户服务。

下完订单后,订单状态为DRAFT,在TCC事务中TRY阶段,订单支付服务将订单状态变成PAYING,同时远程调用红包帐户服务和资金帐户服务,将付款方的余额减掉(预留业务资源);如果在TRY阶段,任何一个服务失败,tcc-transaction将自动调用这些服务对应的cancel方法,订单支付服务将订单状态变成PAY_FAILED,同时远程调用红包帐户服务和资金帐户服务,将付款方余额减掉的部分增加回去;如果TRY阶段正常完成,则进入CONFIRM阶段,在CONFIRM阶段(tcc-transaction自动调用),订单支付服务将订单状态变成CONFIRMED,同时远程调用红包帐户服务和资金帐户服务对应的CONFIRM方法,将收款方的余额增加。特别说明下,由于是示例,在CONFIRM和CANCEL方法中没有实现幂等性,如果在真实项目中使用,需要保证CONFIRM和CANCEL方法的幂等性。

前置条件:所有的流程都必须支持幂等操作。

正常流程

  1. root:创建订单
  2. root:发起付款命令
  3. root:try:更新订单状态
  4. root:try:发起资金账户A检查、红包账户B扣款
  5. A:try:创建交易订单
  6. A:try:检查校验资金账户金额
  7. B:try:创建交易订单
  8. B:try:减去红包账户金额
  9. root:confirm:更新订单状态
  10. root:confirm:触发对方commit
  11. A:confirm:更新交易订单
  12. A:confirm:增加资金账户
  13. B:confirm:更新交易订单
  14. B:confirm:核查红包账户

异常流程

root控制整个cancel调用主权
root在try阶段收到任意方的失败消息,将调用所有放的cancel的方法。
root在confirm阶段出现异常,需要重试。
root在cancel阶段出现异常,需要重试。

多层嵌套

TCC在多层嵌套过程中,同一个角色在嵌套关系上可能既是Provider也是Consumer,作为Consumer是,同样需要充当Leader的角色负责本次调用的协调。

一层

  • A - B
  • A - C

二层

  • B - D
  • B - E

事务持久化

Transaction在Leader和Service应用中会各自持久化一份数据,并在TCC3个阶段更新对应的状态。

  • Try阶段 生成Transaction,持久化Transaction
  • Confirm阶段 找到对应的Transaction,更新Transaction为confirm,执行分支事务confirm,完成则delete,失败则重试执行部分
  • Cancel阶段 找到对应的Transaction,更新Transaction为cancel,执行分支事务confirm,完成则delete,失败则重试执行部分

按照分布式链路Dapper的设计思想,RPC中的事务信息需要满足以下内容:

  • GLOBLE事务信息
  • 本地事务ID
  • 本地事务状态
  • 本地事务confirm信息
  • 本地事务cancel信息

在一套A-B A-C的简单系统中,将会分别存在3条本地事务信息,分别记录并提供各自的TCC3个阶段的信息。

参考:

https://github.com/changmingxie/tcc-transaction