一篇入门分布式事务

前言

在接触分布式事务之前就听讲过好几种分布式解决方案,今天来剖析一下各方案的区别。最常用的应该是MQ的最终一致性的消息事务,在用MQ的过程中,你可能已经实现过分布式事务了。

初识分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

这里我们举个例子:例如在大型电商系统中,下单接口通常会扣减库存、减去优惠、生成订单 id, 而订单服务与库存、优惠、订单 id 都是不同的服务,下单接口的成功与否,不仅取决于本地的 db 操作,而且依赖第三方系统的结果,这时候分布式事务就保证这些操作要么全部成功,要么全部失败。

本质上来说,分布式事务就是为了保证不同数据库的数据一致性

2PC

两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。有以下两个阶段

  • 首先是准备阶段,Coordinator会向所有事务参与者询问资源是否就绪,即有没有线程正在占用,是不是还处在其他事物当中,如果资源就绪了,就进行锁定,即表明这个资源正在参与分布式事务的流程中,将资源锁定不允许修改
  • 然后到提交阶段,Coordinator向所有事务参与者发送提交信号,所有事务参与者就会进行事务的提交,有一个参与者失败则进行回滚
  • 2PC的优点

    尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

  • 2PC的缺点

    1. **同步阻塞:**2PC的缺点也是显而易见的,它是一个强一致性的同步阻塞协议,事务执行过程中需要将所需资源全部锁定。

    2. **单点问题:**协调者在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,参与者就会一直阻塞,导致数据库无法使用。

    3. **数据不一致:**两阶段提交协议虽然为分布式数据强一致性所设计,**但仍然存在数据不一致性的可能,**比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

3PC

3PC是2PC的一种改进版本,为了解决两阶段提交协议的阻塞问题即2PC的缺点,当协调者崩溃时,参与者不能做出最后的选择,就会一直保持阻塞锁定资源的状态。

2PC中只有协调者有超时机制,3PC在协调者和参与者中都引入了超时机制,协调者出现故障后,参与者就不会一直阻塞了。

3PC在2PC的第一阶段和第二阶段中又插入了一个准备阶段 ,保证了在最后提交阶段之前各参与节点的状态是一致的。

虽然3PC用超时机制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差了(不推荐)

TCC

TCC(Try-Confirm-Cancel)分为以下三个阶段:

  1. Try阶段:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
  2. Confirm阶段:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
  3. Cancel阶段:取消执行,释放Try阶段预留的业务资源 Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。

举例子:

如果你用100元买了一个面包。 Try阶段:你需要向你的钱包检查是否够100元并锁住这100元,面包也是一样的。

如果有一个失败,则进行cancel(释放这100元和这个面包),如果cancel失败不论什么失败都进行重试cancel,所以需要保持幂等。

如果都成功,则进行confirm,确认这100元扣,和这个面包被卖,如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)

缺点:

在第2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

消息事务(最终一致性)

非常常用的分布式事务的解决方案,或许你会在使用消息队列的时候不知不觉的做到了分布式事务,只是你没有察觉到

基本流程如下:

  1. 第一阶段:Prepare消息,拿到消息的地址(注意此时没有发送消息)
  2. 第二阶段:执行本地事务
  3. 第三阶段:通过第一阶段拿到的消息地址去发送消息
  • 思考:为什么是先执行本地事务后才进行消息的发送?不能先发送消息再执行本地事务吗?

    如果先发送消息,那本地事务如果出现问题进行回滚了,但是你消息已经发送出去了,是不能将消息会滚的

    所以先执行本地事务,如果本地事务出现问题了就不会进行消息的发送,本地事务成功提交,进行消息发送

    那如何保证消息一定生产成功并保证消息一定能够被消费呢?这就是MQ的作用了,下面我们对MQ进行分析

  • 如果消费失败了怎么办?

    消费失败了则由MQ进行重试,消息接收端需要保证幂等。如果消息消费失败,这个就需要人工进行处理,因为这个概率较低,如果为了这种小概率时间而设计这个复杂的流程反而得不偿失

相关推荐
2401_857610031 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_2 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust
monkey_meng2 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
Estar.Lee3 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
javaDocker3 小时前
业务架构、数据架构、应用架构和技术架构
架构
新知图书3 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放4 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang4 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net