随着微服务架构的流行,随之而来就必然遇到跨服务的分布式事务这个难题。分布式事务之所以难,主要是因为分布式系统中的各个节点都可能发生各种非预期的情况。本文先介绍分布式系统中的异常问题,然后介绍这些问题带给分布式事务的挑战,接下来指出现有各种常见用法的问题,最后给出正确的方案。
分布式系统最大的敌人可能就是 NPC 了,在这里它是 Network Delay, Process Pause, Clock Drift 的首字母缩写。我们先看看具体的 NPC 问题是什么:
分布式事务既然是分布式的系统,自然也有 NPC 问题。因为没有涉及时间戳,带来的困扰主要是 NP 。
我们以分布式事务中的 TCC (如果是对 TCC 还不了解的同学,可以参考这篇文章,分布式事务最经典的七种解决方案,了解分布式事务相关的基础知识)作为例子,看看 NP 带来的影响。
一般情况下,一个 TCC 回滚时的执行顺序是,先执行完 Try ,再执行 Cancel ,但是由于 N ,则有可能 Try 的网络延迟大,导致先执行 Cancel ,再执行 Try 。
这种情况就引入了分布式事务中的两个难题:
分布式事务还有一类需要处理的常见问题,就是重复请求,业务需要做幂等处理。因为空补偿、悬挂、重复请求都跟 NP 有关,我们把他们统称为子事务乱序问题。在业务处理中,需要小心处理好这三种问题,否则会出现错误数据。
我们看到开源项目https://github.com/yedf/dtm之外,包括各云厂商,各开源项目,他们给出的业务实现建议大多类似如下:
上述的这种实现,能够在大部分情况下正常运行,但是上述做法中的“先查后改”在并发情况下是容易掉坑里的,我们分析一下如下场景:
事实上,NPC 里的 P 和 C ,以及 P 和 C 的组合,有很多种的场景,都可以导致上述竞态情况,就不一一赘述了。
虽然这种情况发生的概率不高,但是在金融领域,一旦涉及金钱账目,那么带来的影响可能是巨大的。
PS:幂等控制如果也采用“先查再改”,也是一样很容易出现类似的问题。解决这一类问题的关键点是要利用唯一索引,“以改代查”来避免竞态条件。
下面我们来详解 yedf/dtm 是如何解决这个问题的。
dtm 首创了子事务屏障技术,用于同时解决空补偿、防悬挂、幂等这三个问题,对于 TCC 事务,他的详细工作过程如下:
假如 Try 和 Cancel 的执行时间没有重叠,那么读者容易分析出上述过程能够解决空补偿和悬挂问题。如果出现了 Try 和 Cancel 执行时间重叠的情况,我们看看会发生什么。
假设 Try 和 Cancel 并发执行,Cancel 和 Try 都会插入同一条记录 gid-branchid-try ,由于唯一索引冲突,那么两个操作中只有一个能够成功,而另一个则会等持有锁的事务完成后返回。
综上各种情况的详细论述,子事务屏障能够在各种 NP 情况下,保证最终结果的正确性。
事实上,子事务屏障有大量优点,包括:
上述的理论与分析过程也同样适用于 SAGA 分布式事务。dtm 里面的子事务屏障同时支持了 TCC 和 SAGA 两种事务模式。
DTM 是一款 golang 开发的分布式事务管理器,解决了跨数据库、跨服务、跨语言栈更新数据的一致性问题。
下面是 dtm 和阿里开源的 seata 的主要特性对比:
特性 | DTM | SEATA | 备注 |
---|---|---|---|
支持语言 | Go 、Java 、python 、php 、c#... | Java | dtm 可轻松接入一门新语言 |
异常处理 | 子事务屏障自动处理 | 手动处理 | dtm 解决了幂等、悬挂、空补偿 |
TCC 事务 | ✓ | ✓ | |
XA 事务 | ✓ | ✓ | |
AT 事务 | 建议使用 XA | ✓ | AT 与 XA 类似,性能更好,但有脏回滚 |
SAGA 事务 | 支持并发 | 状态机模式 | |
事务消息 | ✓ | ✗ | dtm 提供类似 rocketmq 的事务消息 |
单服务多数据源 | ✓ | ✗ | |
通信协议 | HTTP 、gRPC | dubbo 等协议 | dtm 对云原生更加友好 |
如果您的语言栈包含了 Java 之外的语言,那么 dtm 是您的首选。如果您的语言栈是 Java ,您也可以选择接入 dtm ,使用子事务屏障技术,简化您的业务编写,可以参考用 Java 轻松完成一个 TCC 分布式事务,自动处理空补偿、悬挂、幂等。
如果您想要学习分布式事务相关的知识,dtm 的文档备受好评,能够让读者快速入门分布式事务,理论结合实践,让读者逐步深入。
欢迎大家访问https://github.com/yedf/dtm,欢迎 Issue 、PR 、Star