参考:
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种类型:
- 优先考虑避免分布式事务:可以将两个子系统的数据库表放在同一个从库下时:直接使用mysql事务,避免分布式事务;
- 无法放在同一个从库下时: 使用TCC;
- 特殊限制下: TCC+MQ;
- 只要求最终一致性时: 使用MQ异步处理,反复重试。
2PC和TCC的区别:
2PC: 数据库层,性能差(数据库锁);
TCC: 应用层,性能高,开发成本高(需要保证幂等性)。
TCC
TCC: Try-Cancel-Commit
开源实现: ByteTCC
1 | 1、Try:尝试执行业务。 |
其中Confirm和cancel接口需要是幂等的。
每个子事务实现TCC的几个幂等接口。
- 使用try锁定资源;
- 所有子事务: 写redo日志(持久化)\undo日志(回滚), 执行操作;
- 所有子事务: commit提交;
- 失败则cancel取消。
特例
最后一个子事务可以只实现TryCommit合并:
1 | IF(代金券.Try) { |
特殊限制下
超强一致性:两个从库之间转账。
特殊限制:
加钱: 可以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,反复异步重试直到成功(削峰、错峰重试(随机探测))