分布式事务简述

什么是事务?

通常给出的定义是数据库的执行逻辑单元。这句话感觉跟没说一样。
我们平时用到事务,主要是用到它的四个特性(ACID)。
我去看待问题的时候,通常会去问一些“傻”的问题:那为什么是这4个特性,不是其他的呢?或者为什么是4个,而不是3个或5个呢?
这4个特性被定义出来后,后续出来的数据库需要满足这4个特性,才可以说自己支持了事务。

什么叫分布式事务?

我们知道事务的作用范围是数据库,即一个事务内的数据库操作都是对同一个数据库进行的。
当一个“事务”内的数据库操作是对多个数据库进行的,这个“事务”就叫做分布式事务。

实际应用中通常情况下是以下两种情况:

  • 一个系统的某个操作涉及多个数据源。但又希望这个操作本身满足事务的特性,这种需求就是分布式事务的需求,满足这种需求的事务就叫分布式事务。

  • 同样地,在这个微服务盛行(烂大街)的年代,一个操作可能涉及很多个服务系统,每个服务有自己的数据源。但又希望这个操作本身满足事务的特性,这种也叫做分布式事务。

其实第一说法并不常见,因为按照微服务的单一职责原则来说,一个系统访问多个数据源还要保证事务性,要么你的数据库设计有问题,要么需要拆服务了。

如何实现分布式事务?

本质上有两种思想:

  • 引入一个事务协调者
    希望达到的效果是:由协调者协调多个事务,要提交大家一起提交,要回滚大家一起回滚。
  • TCC
    抛开数据库事务束缚,使用业务数据的状态来达到事务的特性。

这两个思想下面进行详细描述。

事务协调者

最早的分布式事务模型是 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常说的 X/Open XA 协议,简称XA 协议。
即引入一个事务协调者,将对数据库的提交/回滚转交给协调者进行操作。


分布式事务

这个方案整体看起来还行。
但有一个问题:如果4.1提交失败,4.2提交成功。这个时候,我们怎么办?
4.1提交重试是一个办法,但如果一直重试失败怎么办?
这个时候,4.2已经提交成功,又不可能再回滚了。这个时候就数据不一致了,很严重。
对于应对这种情况,有人提出了2pc和3pc的思想。注意,这里强调一下,2pc/3pc是为了应对提交失败的情况的改进。

2pc

说得简单点,就是提交这个操作分为两步,先去prepare一下,每个数据源反馈一下,提交大概率会成功还是失败。如果成功,那么就都提交,如果有一个说大概率会失败,就回滚。


2pc
缺点:
  • 性能问题,在执行期间所有数据源都不能提交,资源锁定。
  • 单点,协调者成为单点。
  • 数据不一致,网络分区后,部分数据源收不到提交请求。
  • 网络分区后,由于部分数据源收不到提交/回滚请求,可能导致该数据库资源一直得不到释放的情况,引起资源的严重浪费。

3pc

在2pc的prepare和commit之间又加了一个cancommit阶段,并且加了超时机制。

  • 加了一个阶段的好处,将提交成功率的概率再次提升。因为concommit又做了一些undo日志的事情。
  • 超时机制的目的是对应应对2pc的第4个缺点。
    协调者的超时,即收不到参与者的ack,则认为是没有成功。
    参与者的超时,在cancommit和commit之间,一直没有收到commit,则自行提交,避免占用锁。
缺点

2pc的3个缺点仍然存在,其中因引入超时机制,参与者可自行提交。可能导致数据不一致现象更加突出。

XA优点

说一下XA的优点吧,
因为是参与者的操作依赖于本地事务,所以天然具有事务的一些特性。

XA缺点

性能资源问题
中心化问题
数据不一致问题

TCC

核心点在于,不依赖事务管理器,依靠业务逻辑的分解来完成。
说得简单点,XA会hold住各个本地事务不提交,TCC不会,通过业务逻辑分解来实现业务层面的事物特性。
TCC = try + confirm + cancel
即分布式事务参与者需要提供

  • try:完成所有的业务校验和大部分的逻辑处理。这个阶段几乎是要完成所有的业务处理的,只是会预留一些状态(表示中间状态)。后面的cc只是很薄的一层,理论上只是改一些状态就可以了。
  • confirm:提交。修改try阶段的落地数据状态。理论上是一定要成功的,就算一次不成功,重试也要成功。所以这个阶段一定要保证幂等。
  • cancel:取消也叫回滚。删除或修改try阶段的落地数据状态。同样的,理论上也必须保证成功。
分布式事务的发起者
image.png

分布式事务的框架实现粗略图。
发起者应用起本地事务,事务的开始调用分布式事务的框架进行开启一个分布式事务(注册两个事务同步器,如果本地事务提交/回滚,则调用分布式事务参与者的对应方法)。
然后发起者程序里,正常调用A和B,如果A和B失败,则需要回滚本地事务。而从触发框架的提交/回滚操作。

分布式事务补偿

虽然说分布式事务的参与者的两个CC方法,需要保证一定成功。
但这个世界不是完美的,一定会出现某个参与者的CC方法失败的情况,这个时候就需要一种分布式事务的补偿机制。
即分布式事务开启时需要记录该事务的状态,以及对应参与者的信息。
当提交成功后,方可删除,如果提交/回滚失败,则需要定时任务来进行重试。
这一步必不可少,公司实现生产中,很多依靠补偿机制来完成数据的一致性保障。

空回滚问题

在实际生产中,会遇到这类问题,作为一个分布式事务的参与者,先收到了cancel请求,然后再收到了try请求。
这种情况发生的概率还不小,这也是tcc的一种常见的异常情况。

  • 为什么会出现这种情况?
    发起者调用参与者时,由于网络原因或其他原因(积压),try请求一直没到参与者系统中。
    然后发起者此时认为参与者超时了,发起了cancel请求。
    所以无法保障try一定先于cancel,但可以保证try一定先于confirm(这个留给大家思考下原因)。
  • 造成的结果
    cancel到了之后,发现没有try,然后直接空处理。
    后续try来了后,处理成功,然后这个请求一直没有confirm/cancel了。导致一直处于事务的悬挂状态。
  • 解决办法
    • 分布式锁
      try来了后,加锁,完成后解锁。
      cancel来了后,加锁3分钟(如果try没完成会失败),完成后不解锁。
      此办法可解决cancel先来了,3分钟内try再来会直接失败掉,解决空回滚的问题。
    • 前置事务表(推荐)
      同样的道理。
      try来了后,插入P,完成后删除。
      cancel来了后,插入R(如果是P则失败),完成后不删除。
      cancel先来了,插入R,后续try来了后插入P失败,解决空回滚问题。

优点

不依赖事务管理器,所以不存在事务资源性能的问题。

缺点

  • 使得业务接口变得复杂。
    之前一个接口,现在需要提供3个接口。
  • 数据不一致
    cc的两个接口必须各个业务系统实现幂等和一定成功。如果业务系统打破了,就会造成数据不一致的现象。
  • 空回滚与悬挂问题

总结

这里主要说了两种分布式事务的理论:XA协议与TCC。
分析了两种理论的原理以及优缺点。
由于目前公司的分布式事务主要是TCC实现,所以对此比较熟悉。

这里也推荐大家
实际生产中,能不要用分布式事务的话,尽量不要用,会给系统带来太多的复杂性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容