分布式事务了解吗?你们是如何解决分布式事务问题的

462 阅读7分钟

分布式事务

事务的四⼤特性(ACID):
原⼦性(Atomicity):事务作为⼀个整体被执⾏,包含在其中的对数据库的操作要么全部被执⾏,要么 都不执⾏。
⼀致性(Consistency):事务应确保数据库的状态从⼀个⼀致状态转变为另⼀个⼀致状态。⼀致状态 是指数据库中的数据应满⾜完整性约束。除此之外,⼀致性还有另外⼀层语义,就是事务的中间状态不 能被观察到(这层语义也有说应该属于原⼦性)。
隔离性(Isolation):多个事务并发执⾏时,⼀个事务的执⾏不应影响其他事务的执⾏,如同只有这⼀ 个操作在被数据库所执⾏⼀样。
持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。在事务结束时,此 操作将不可逆转。
单机事务是通过将操作限制在⼀个会话内通过数据库本身的锁以及⽇志来实现ACID,那么分布式环境下 该如何保证ACID特性呢?
当我们的单个数据库的性能产⽣瓶颈的时候,我们可能会对数据库进⾏分区,这⾥所说的分区指的是物 理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这 种情况了,⽽在这种ACID的集群环境下,再想保证集群的ACID⼏乎是很难达到,或者即使能达到那么 效率和性能会⼤幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致 我们的系统变得很差,这时我们就需要引⼊⼀个新的理论原则来适应这种集群的情况,就是 CAP 原则 或者叫CAP定理。

分布式事务解决⽅案

现在的分布式事务实现⽅案有多种,有些已经被淘汰,如基于XA的两段式提交、TCC解决⽅案,还有本 地消息表、MQ事务消息,还有⼀些开源的事务中间件,如LCN、GTS。

1、基于XA的两阶段提交⽅案   

XA 它包含两个部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,⽐ 如 Oracle、DB2 这些商业数据库都实现了 XA 接⼝,⽽事务管理器作为全局的协调者,负责各个本地资 源的提交和回滚。   
两阶段提交⽅案应⽤⾮常⼴泛,⼏乎所有商业OLTP (On-Line Transaction Processing)数据库都⽀ 持XA协议。但是两阶段提交⽅案开发复杂、锁定资源时间⻓,对性能影响很⼤,基本不适合解决微服务 事务问题。

2、TCC解决⽅案

TCC⽅案在电商、⾦融领域落地较多。TCC⽅案其实是两阶段提交的⼀种改进。其将整个业务逻辑的每 个分⽀显式的分成了Try、Confirm、Cancel三个操作。

  • Try 阶段主要是对业务系统做检测及资源预留,完成业务的准备⼯作。
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执⾏成功并开始执⾏ Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm⼀定成功。
  • Cancel 阶段主要是在业务执⾏错误,需要回滚的状态下执⾏的业务取消,预留资源释放。 事务开始时,业务应⽤会向事务协调器注册启动事务。之后业务应⽤会调⽤所有服务的try接⼝,完成⼀ 阶段准备。之后事务协调器会根据try接⼝返回情况,决定调⽤confirm接⼝或者cancel接⼝。如果接⼝ 调⽤失败,会进⾏重试。
    微服务倡导服务的轻量化、易部署,⽽TCC⽅案中很多事务的处理逻辑需要应⽤⾃⼰编码实现,复杂且 开发量⼤。

3、本地消息表 (异步确保)

本地消息表其实是国外的 ebay 搞出来的这么⼀套思想。
这个⼤概意思是这样的:

  1. A 系统在⾃⼰本地⼀个事务⾥操作同时,插⼊⼀条数据到消息表;
  2. 接着 A 系统将这个消息发送到 MQ 中去;
  3. B 系统接收到消息之后,在⼀个事务⾥,往⾃⼰本地消息表⾥插⼊⼀条数据,同时执⾏其他的业务 操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
  4. B 系统执⾏成功之后,就会更新⾃⼰本地消息表的状态以及 A 系统消息表的状态;
  5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描⾃⼰的消息 表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
  6. 这个⽅案保证了最终⼀致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为 ⽌。 这个⽅案说实话最⼤的问题就在于严重依赖于数据库的消息表来管理事务啥的,会导致如果是⾼并发场 景咋办呢?咋扩展呢?所以⼀般确实很少⽤。

4、MQ事务消息

直接基于 MQ 来实现事务,不再⽤本地的消息表。有⼀些第三⽅的MQ是⽀持事务消息的,⽐如 RocketMQ,他们⽀持事务消息的⽅式也是类似于采⽤的⼆阶段提交,但是市⾯上⼀些主流的MQ都是 不⽀持事务消息的,⽐如 RabbitMQ 和 Kafka 都不⽀持。 实现思想为:

  • A 系统先发送⼀个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作 别执⾏了;
  • 如果这个消息发送成功过了,那么接着执⾏本地事务,如果成功就告诉 mq 发送确认消息,如果失 败就告诉 mq 回滚消息;
  • 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执⾏本地的事务;
  • mq 会⾃动定时轮询所有 prepared 消息回调你的接⼝,问你,这个消息是不是本地事务处理失败 了,所有没发送确认的消息,是继续重试还是回滚?⼀般来说这⾥你就可以查下数据库看之前本地 事务是否执⾏,如果回滚了,那么这⾥也回滚吧。这个就是避免可能本地事务执⾏成功了,⽽确认 消息却发送失败了。 这个⽅案⾥,要是系统 B 的事务失败了咋办?重试咯,⾃动不断重试直到成功,如果实在是不 ⾏,要么就是针对重要的资⾦类业务进⾏回滚,⽐如 B 系统本地回滚后,想办法通知系统 A 也回 滚;或者是发送报警由⼈⼯来⼿⼯回滚和补偿。   
  • 这种⽅案缺点就是实现难度⼤,⽽且主流MQ不⽀持。

5、分布式事务中间件解决⽅案

分布式事务中间件其本身并不创建事务,⽽是基于对本地事务的协调从⽽达到事务⼀致性的效果。典型 代表有:阿⾥的GTS(www.aliyun.com/aliware/txc… image.png

总结

分布式事务本身是⼀个技术难题,是没有⼀种完美的⽅案应对所有场景的,具体还是要根据业务场景团 队讨论选择。