详谈分布式事务

简介: 详谈分布式事务

前言

本文是前面一篇文章聊了基于sharding的分库分表后拓展出来的关于分布式事务的讨论,本文中将详细讲一下sharding中的分布式事务以及市面上常见的分布式事务解决方案。

1.sharding的分布式事务

Sharding JDBC: Sharding JDBC 提供了对分布式事务的支持。它通过两阶段提交(2PC)协议来实现跨多个数据源的分布式事务。Sharding JDBC 会将事务中的操作发送到各个数据源执行,并在提交阶段协调所有数据源的提交操作,以保证事务的一致性。


Sharding Proxy: Sharding Proxy 本身并不直接支持分布式事务。它主要是一个中间件,负责路由和转发客户端的 SQL 请求到后端的多个数据源。在 Sharding Proxy 中,跨多个数据源的事务操作会被拆分成多个独立的本地事务,每个数据源独立处理。这就意味着在 Sharding Proxy 中,跨多个数据源的事务并不会保证全局的一致性,需要应用程序自行处理分布式事务的逻辑。


sharing jdbc后在spring boot中如何使用分布式事务,代码示例:

@Service
public class MyService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Transactional
    public void performDistributedTransaction() {
        // 在事务范围内执行数据库操作
        jdbcTemplate.update("INSERT INTO table1 (column1) VALUES (?)", "value1");
        jdbcTemplate.update("INSERT INTO table2 (column1) VALUES (?)", "value2");
        // 手动抛出异常,模拟事务中的异常情况
        throw new RuntimeException("Simulated exception");
    }
}
 

是的你没看错,不用任何额外的配置,就是用spring的事务即可。因为sharding jdbc作为一个分库分表的技术栈,天生就有分布式事务问题,所以其默认就是开启分布式事务的,它的事务都是两阶段提交的。

说到分布式事务,我们来回顾一下之前聊过的分布式事务的内容。

2.分布式事务的产生原因

分布式事务是指在在分布式架构下一个事务中的不同子操作是在不同机器上执行的,子操作之间状态无法相互感知,其中有子操作出错后其他机器上的子操作无法正确回滚。

分布式事务产生的根本原因:各个服务之间无感知

举一个例子:

一个下单事务,包含创建订单,库存服务,需要顺序调用订单服务、库存服务。如果库存服务挂掉后,订单服务也是无法回滚的,因为订单服务感知不到库存服务是否成功,即无法感知到库存服务的状态。


3.分布式事务的解决方案

3.1.DTP模型

1994年,X/Open组织(即现在的Open Group)定义了分布式事务处理的DTP模型,该模型包括几个角色:

  • 应用程序(AP),服务
  • 事务管理器(TM),全局事务管理者
  • 资源管理器(RM),一般是数据库
  • 通信资源管理器(CRM),TM和RM之间的通信中间件

DTP模型中,一个分布式事务(全局事务)可以被拆分成多个本地事务,运行在不同的AP和和RM上。每个本地事务的ACID由自身保证,全局事务必须保证每个本地事务必须同时成功,若有一个失败其他事务必须回滚。由于本地事务之间相互是无法感知状态的,因此要通过CRM来通知各个本地事务,同步事务执行的状态。


由于各个本地事务之间需要通信,通信就需要标准,因此配套提出了XA通信标准。XA规定了DTP中CRM和TM之间的通信的接口规范,定义了用于通知事务开始、提交、终止、回滚等接口,各个数据库厂商之间必须实现该接口。

3.2.分阶段提交

分阶段提交,也叫两阶段提交(two-phase commit)。DTP模型落地实现的一种思想。

两阶段提交中,将事务分成两个阶段来执行:

  • 阶段一,准备阶段,各个本地事务执行自己事务内的逻辑,完成提交前的准备工作。
  • 阶段二,执行阶段,各个事务根据上一阶段的执行结果,进行提交或者回滚。

两阶段提交中有两个角色,协调者(coordinator)、参与者(voter)。

正常情况:

异常情况:

准备阶段协调者会询问每个参与者是否可以执行事务,每个事务参与者执行事务写入redo、undo日志,然后反馈事务的执行结果,只要有一个参与者返回的是disagree则说明失败,协调者会像各个参与者发出abort指令,各个事务收到指令后各自回滚事务。


两阶段提交的优点:

  • 方案成熟,很稳
  • 能保证强一致性

两阶段提交的缺点:

  • 单点故障,当协调者挂了之后后续的步骤就没办法进行了,被锁住的数据没办法解锁,会造成阻塞。
  • 数据锁定,分阶段提交由于过程被拆成两段,会造成本地事务的执行过程变长,数据会长时间处于锁定状态。

3.3.TCC模式

TCC模式是专门用来解决分阶段提交的缺陷的,减少数据锁定,避免阻塞问题。

TCC相当于是强化了两阶段提交,在分阶段提交中埋入了以下几个点位:

  • try,资源的检测和预留。
  • confirm,执行的业务操作提交,要求try成功的话confrim一定要能成功。
  • cancel,预留资源释放

经过TCC的强化埋点后两阶段提交变成了:

  • 准备阶段。try,资源预留,通知每个参与者去try一下数据资源,判断一下数据资源是否足以支撑之后的事务运行。每个参与者均try成功再执行下一步,否则直接cancel。
  • 执行阶段,confrim,让协调者通知各个参与者执行并提交事务(因为资源已经准备好了,执行和提交可以一气呵成),各个参与者confrim成功则成功,但凡有一个失败则通知协调者组织全局回滚、释放资源、退出。

TCC的优点:

TCC其实每个阶段都是单独的事务,try其实是个事务只是去摸一下看下阶段要的数据资源是否能用,confrim则是原来的包含业务逻辑的事务。这样的话就避免了两阶段提交中agree过程中对于数据的锁定,尽量避免了影响其他事务的执行。

TCC的缺点:

  • 编码成本,存在代码侵入需要开发人员手动编写TCC三步,开发的成本会比较高。
  • 弱一致性,TCC保证的是最终一致性,因为单个本地事务的执行和提交都包含在confirm一个步骤中,如果出现问题回滚前其实是没有保证一致性的,在这中间有其他读操作进来是能读到已经变动的数据的。

3.4.可靠消息服务


可靠消息服务起源自eBay,是将各个服务的本地事务串成一个链,依托MQ来保障分布式事务,比如服务A执行完服务后向MQ中存放自己执行成功的信息,MQ再向服务B推送消息叫它执行本地事务,服务B执行完本地事务后又告诉MQ,MQ继续向后续要执行本地事务的服务推送消息。

缺点:

由于是MQ主动向后续服务推送消息,后续服务要是失败了,前置服务感知不到,前置服务无法进行感知回滚。。所以可靠消息服务适用的场景有限。

3.5.AT模式

AT模式是Alibaba的seata组件开源出来的一种分布式事务解决方案,是对TCC的一种优化,解决了TCC模式中的的代码侵入、编码复杂等问题。


AT模式中,用户只需关注自己的业务SQL,seata框架会自动生成书屋的二阶段提交和回滚操作。


AT模式整个流程和TCC大致相同,不同点是在于AT模式将执行放在了一阶段:

  • 一阶段(开发者实现),正常编写业务逻辑,然后执行SQL,执行本地事务,并返回执行结果。
  • 二阶段(框架托管),根据一阶段的结果判断二阶段的做法,提交或者回滚。

AT模式的底层原理:

在阶段一的时候拦截下所有SQL,框架会去解析这条SQL然后去数据库中查询出要操作的数据,存一份镜像叫before image,接下来执行SQL,执行完SQL后再查询一次,存一份叫after image。


到了阶段二的时候,比对before镜像和after镜像从而判断操作是否成功,如果一阶段的所有本地事务操作都是成功的,就会清空before镜像和after镜像,如果有一个是失败的各个本地事务就会根据属于自己的before镜像进行回滚。


after镜像是为了在回滚的时候进行比对,要是回滚时发现数据库中此时的数据和after镜像中记录的数据不相同,则说明数据又被动过了,产生了脏数据,此时就需要人工介入了。由于行锁的存在,产生脏数据的概率很低很低,after镜像只是留了一手。

AT模式中的角色:

AT模式中一共有三个角色

  • TC,服务协调者,是一个单独的服务。
  • TM,通信中间件。
  • RM,资源管理器,管理分支的事务处理,与TC进行通信注册分支事务和报告事务状态,驱动分支事务提交或者回滚。

TM、RM和服务是耦合在一起的,但是不侵入代码,以jar包的方式体现。

3.6.Seata


Seata是目前为止常用、流行切稳定的分布式事务解决方案,其在使用上对代码没有侵入,直接是基于配置的,使用方法见官方手册即可。在遇到分布式事务场景时,可以优先考虑使用seata来解决。

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
存储 缓存 NoSQL
详谈分布式系统缓存的设计细节
在分布式Web程序设计中,解决高并发以及内部解耦的关键技术离不开缓存和队列,而缓存角色类似计算机硬件中CPU的各级缓存。如今的业务规模稍大的互联网项目,即使在最初beta版的开发上,都会进行预留设计。但是在诸多应用场景里,也带来了某些高成本的技术问题,需要细致权衡。
1533 0
|
3月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
5月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
147 2
基于Redis的高可用分布式锁——RedLock
|
5月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
1月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
140 5
|
2月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
79 8
|
2月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
64 16
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
51 5
|
3月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
83 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
3月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
60 1