分布式事务的这些常见用法都有坑,来看看正确姿势

2021-12-06 10:17:37 +08:00
 dongfuye1

随着微服务架构的流行,随之而来就必然遇到跨服务的分布式事务这个难题。分布式事务之所以难,主要是因为分布式系统中的各个节点都可能发生各种非预期的情况。本文先介绍分布式系统中的异常问题,然后介绍这些问题带给分布式事务的挑战,接下来指出现有各种常见用法的问题,最后给出正确的方案。

NPC 的挑战

分布式系统最大的敌人可能就是 NPC 了,在这里它是 Network Delay, Process Pause, Clock Drift 的首字母缩写。我们先看看具体的 NPC 问题是什么:

分布式事务既然是分布式的系统,自然也有 NPC 问题。因为没有涉及时间戳,带来的困扰主要是 NP 。

TCC 的空补偿与悬挂

我们以分布式事务中的 TCC (如果是对 TCC 还不了解的同学,可以参考这篇文章,分布式事务最经典的七种解决方案,了解分布式事务相关的基础知识)作为例子,看看 NP 带来的影响。

一般情况下,一个 TCC 回滚时的执行顺序是,先执行完 Try ,再执行 Cancel ,但是由于 N ,则有可能 Try 的网络延迟大,导致先执行 Cancel ,再执行 Try 。

这种情况就引入了分布式事务中的两个难题:

  1. 空补偿:Cancel 执行时,Try 未执行,事务分支的 Cancel 操作需要判断出 Try 未执行,这时需要忽略 Cancel 中的业务数据更新,直接返回
  2. 悬挂:Try 执行时,Cancel 已执行完成,事务分支的 Try 操作需要判断出 Cancel 一致性,这时需要忽略 Try 中的业务数据更新,直接返回

分布式事务还有一类需要处理的常见问题,就是重复请求,业务需要做幂等处理。因为空补偿、悬挂、重复请求都跟 NP 有关,我们把他们统称为子事务乱序问题。在业务处理中,需要小心处理好这三种问题,否则会出现错误数据。

现有方案的问题

我们看到开源项目https://github.com/yedf/dtm之外,包括各云厂商,各开源项目,他们给出的业务实现建议大多类似如下:

上述的这种实现,能够在大部分情况下正常运行,但是上述做法中的“先查后改”在并发情况下是容易掉坑里的,我们分析一下如下场景:

事实上,NPC 里的 P 和 C ,以及 P 和 C 的组合,有很多种的场景,都可以导致上述竞态情况,就不一一赘述了。

虽然这种情况发生的概率不高,但是在金融领域,一旦涉及金钱账目,那么带来的影响可能是巨大的。

PS:幂等控制如果也采用“先查再改”,也是一样很容易出现类似的问题。解决这一类问题的关键点是要利用唯一索引,“以改代查”来避免竞态条件。

正确姿势

下面我们来详解 yedf/dtm 是如何解决这个问题的。

dtm 首创了子事务屏障技术,用于同时解决空补偿、防悬挂、幂等这三个问题,对于 TCC 事务,他的详细工作过程如下:

  1. 在本地数据库中创建好子事务屏障表 dtm_barrier.barrier ,唯一索引为 gid-branchid-branchop
  2. 对于 Try 、Confirm 、Cancel 操作,insert ignore 一条记录 gid-branchid-try|confirm|cancel ,如果影响行数为 0 (重复请求、悬挂),直接提交返回
  3. 对于 Cancel 操作额外再 insert ingore 一条记录 gid-branchid-try ,如果影响行数为 1 (空补偿),直接提交返回
  4. 执行业务逻辑并提交返回,如果业务发生错误则回滚

假如 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

2202 次点击
所在节点    推广
1 条回复
tienyc
2021-12-10 17:07:34 +08:00
好文章

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/820265

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX