分布式事务

参考:
https://segmentfault.com/a/1190000016397619#item-1-1
https://www.cnblogs.com/duanxz/p/5226316.html

文中提到的主要是微信支付数据库层面的分布式事务优化。

优化前

架构上分为:
客户端;
CN: Coordinator,协调节点, 类似于对外的服务接口、代理,帮忙协调锁资源;
GTM: Global Transaction Manager, 类似于全局锁,全局事务管理器
DN: 具体数据节点;

逻辑很简单,就是CN每次向GTM申请锁,确保分布式事务的安全;
CN估计有多个,可能是react模式。
这种锁就像直接synchronized了全局,并发纯靠GTM单点纵向拓展。

优化后

锁下放到DN层。

分布式事务

分布式事务: 多个子系统一致成功、或一致失败回滚。

4种类型:

  1. 优先考虑避免分布式事务:可以将两个子系统的数据库表放在同一个从库下时:直接使用mysql事务,避免分布式事务;
  2. 无法放在同一个从库下时: 使用TCC;
  3. 特殊限制下: TCC+MQ;
  4. 只要求最终一致性时: 使用MQ异步处理,反复重试。

2PC和TCC的区别:
2PC: 数据库层,性能差(数据库锁);
TCC: 应用层,性能高,开发成本高(需要保证幂等性)。

TCC

TCC: Try-Cancel-Commit
开源实现: ByteTCC

1
2
3
4
5
6
7
8
9
10
11
1、Try:尝试执行业务。
完成所有业务检查(一致性)
预留必须业务资源(准隔离性)

2、Confirm:确认执行业务。
真正执行业务
不做任何业务检查
只使用Try阶段预留的业务资源

3、Cancel:取消执行业务
释放Try阶段预留的业务资源

其中Confirm和cancel接口需要是幂等的。

每个子事务实现TCC的几个幂等接口。

  1. 使用try锁定资源;
  2. 所有子事务: 写redo日志(持久化)\undo日志(回滚), 执行操作;
  3. 所有子事务: commit提交;
  4. 失败则cancel取消。

特例

最后一个子事务可以只实现TryCommit合并:

1
2
3
4
5
6
7
IF(代金券.Try) {
IF(现金支付.TryAndCommit) {
代金券.Commit
} {
代金券.Cancel
}
}

特殊限制下

超强一致性:两个从库之间转账。
特殊限制:

加钱: 可以try\commit\cancel;
减钱: 不能try,只能tryCommit(也就是一步到位,不能打日志反复重试(不让记录redo log(什么?你想记录用户的密码?)),不能cancel(有undo日志也无法回滚)),能失败。

方案

方案1: 从库A先+100,然后从库B-100,最后都提交。
方案2: 从库B先-100,然后从库A+100,最后都提交。

方案1, 需要平台先垫钱,肯定是不行的。
方案2, 如果-100的操作是无法try的(比如依赖银行系统),只能直接tryCommit,也就是只能位于末尾,否则就无法符合TCC的模式了。

方案3:
从库A+100: Try;
从库B-100: TryCommit;
从库A+100: Commit;

方案3可行,略麻烦。
方案3的简化:
从库B-100: TryCommit;
发布事件到MQ;
从库A订阅事件,反复重试+100。

最终一致性

将只需要最终一致性的子事务全部放在MQ中。
(类似发通知之类的事件)
发布事件到MQ,反复异步重试直到成功(削峰、错峰重试(随机探测))

推荐文章