前言 本文小新为大家带来 分布式事务组件Seata 相关知识,具体内容包括分布式事务简介(包括:事务简介,本地事务,分布式事务典型场景,分布式事务理论基础,分布式事务解决方案),分布式事务Seata
本文小新为大家带来 分布式事务组件Seata 相关知识,具体内容包括分布式事务简介
(包括:事务简介
,本地事务
,分布式事务典型场景
,分布式事务理论基础
,分布式事务解决方案
),分布式事务Seata使用
(包括:Seata是什么
,Seata的三大角色
,Seata的设计思路
,Seata的设计亮点
,Seata存在的问题
),Seata快速开始
(包括:Seata Server(TC)环境搭建
,业务系统集成Client
)等进行详尽介绍~
不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!
📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)
↩️本文上接:Spring Cloud Alibaba全家桶(八)——Sentinel规则持久化
事务(Transaction) 是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组sql语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。 这四个属性通常称为ACID特性。
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务
(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所
示:
在JDBC编程中,我们通过java.sql.Connection
对象来开启、关闭或者提交事务。代码如下所
示:
Connection conn = ... //获取数据库连接conn.setAutoCommit(false); //开启事务try{ //...执行增删改查sql conn.commit(); //提交事务}catch (Exception e) { conn.rollback();//事务回滚}finally{ conn.close();//关闭链接}
当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完
成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。
典型的分布式事务场景:
跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。笔者见过
一个相对比较复杂的业务,一个业务中同时操作了9个库。下图演示了一个服务同时操作2个库的情
况:
通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如
下图,将数据库B拆分成了2个库:
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于sql:insert into user(id,name) values (1,"张三"),(2,"李四")
。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。
但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都
失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。
微服务架构是目前一个比较一个比较火的概念。例如上面笔者提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过rpc框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:
Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B 又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是 典型的分布式事务场景。
小结: 上述讨论的分布式事务场景中,无一例外的都直接或者间接的操作了多个数据库。如何保证事务的ACID特性,对于分布式事务实现方案而言,是非常大的挑战。同时,分布式事务实现方案还必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。
解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。
由于三阶段提交协议3PC非常难实现,目前市面主流的分布式事务解决方案都是2PC协议。2PC具有普适性——协议一样的存在,目前绝大多数分布式解决方案都是以两阶段提交协议2PC为基础的。
2PC(两阶段提交,Two-Phase Commit)
顾名思义,分为两个阶段:Prepare 和 Commit。
🍀(1)Prepare:提交事务请求
基本流程如下图:
🍀(2)Commit:执行事务提交
执行事务提交分为两种情况,正常提交和回退。
正常提交事务
流程如下图:
中断事务
在执行 Prepare 步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的 YES 响应,或者某个参与者返回了 No 响应,此时,协调者就会进入回退流程,对事务进行回退。流程如下图红色部分(将 Commit 请求替换为红色的 Rollback 请求):
2PC 存在的问题:
常见分布式事务解决方案:
他们有一个共同点,都是“两阶段(2PC)”。“两阶段”是指完成整个分布式事务,划分成两个步骤完成。
实际上,这四种常见的分布式事务解决方案,分别对应着分布式事务的四种模式:AT、TCC、Saga、XA;四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品,可能就是我们常见的(全局事务、基于可靠消息、 大努力通知、TCC)。
下面我们分别来看4种模式(AT、TCC、Saga、XA)的分布式事务实现。
🍀(1)AT模式(auto transcation)
AT 模式是一种无侵入的分布式事务解决方案。
阿里seata框架,实现了该模式。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
AT 模式如何做到对业务的无侵入 :
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能
轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
🍀(2)TCC 模式(Try、Confirm and Cancel)
TCC 模式特点:
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
TCC 三个方法描述:
对于TCC模式的实践过程中要注意以下事项:
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方
法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入
性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这
时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。
悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而 终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。
幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。
🍀(3)saga模式
Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。
saga模式的实现,是长事务解决方案。
Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正
补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
如图:T1-T3都是正向的业务流程,都对应着一个冲正逆向操作C1-C3
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
Saga 模式使用场景:
Saga模式的优势:
Saga模式的缺点:
与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:
🍀(4)XA模式
XA是X/Open DTP组织(X/Open DTP group)定义的两阶段提交协议,XA被许多数据库(如
oracle、DB2、SQL Server、Mysql)和中间件等工具(如CICS 和 Tuxedo)本地支持 。
X/Open DTP模型(1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)。
XA接口函数由数据库厂商提供。XA规范的基础是两阶段提交协议2PC。
JTA(Java Transaction api) 是Java实现的XA规范的增强版 接口。
在XA模式下,需要有一个[全局]协调器,每一个数据库事务完成后,进行第一阶段预提交,并通知协
调器,把结果给协调器。协调器等所有分支事务操作完成、都预提交后,进行第二步;第二步:协调器通知每个数据库进行逐个commit/rollback。其中,这个全局协调器就是XA模型中的TM角色,每个分支事务各自的数据库就是RM。
MySQL 提供的XA实现:https://dev.mysql.com/doc/refman/5.7/en/xa.html
XA模式下的开源框架有: atomikos,其开发公司也有商业版本。
XA模式缺点: 事务粒度大。高并发下,系统可用性低。因此很少使用。
🍀(5)(AT、TCC、Saga、XA)模式分析
四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景:
总结:
分布式事务本身就是一个技术难题,业务中具体使用哪种方案还是需要不同的业务特点自行选择,但是我们也会发现,分布式事务会大大的提高流程的复杂度,会带来很多额外的开销工作,代码量上去了,业务复杂了,性能下跌了。
所以,当我们真实开发的过程中,能不使用分布式事务就不使用。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供 AT、TCC、SAGA 和XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)
官网: https://seata.io/zh-cn/index.html
源码: https://github.com/seata/seata
官方Demo: https://github.com/seata/seata-samples
在 Seata 的架构中,一共有三个角色:
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
在 Seata 中,一个分布式事务的生命周期如下:
AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图。
第一阶段业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库,这是怎么做的呢?
先抛出一个概念DataSourceProxy代理数据源,通过名字大家大概也能基本猜到是什么个操作,后面做具体分析参考官方文档: https://seata.io/zh-cn/docs/dev/mode/at-mode.html
第二阶段分布式事务操作成功,则TC通知RM异步删除undolog。
分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
整体执行流程:
相比与其它分布式事务框架,Seata架构的亮点主要有几个:
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?
相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。
Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。
Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。
Seata部署指南:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
🍀步骤一:下载安装包
下载地址:https://github.com/seata/seata/releases
目前最新的版本为1.6.1:
🍀步骤二:存储模式配置
启动包: seata–>conf–>application.yml,修改store.mode=“db或者Redis”
源码: 根目录–>seata-server–>resources–>application.yml,修改store.mode=“db或者redis”
0以下版本:
启动包: seata–>conf–>file.conf,修改store.mode=“db或者redis”
源码: 根目录–>seata-server–>resources–>file.conf,修改store.mode=“db或者redis”
🍀步骤三:建表(仅db存储模式)
新建表: 可以去seata提供的资源信息中下载:https://github.com/seata/seata/blob/1.6.1/script/server/db/mysql.sql
🍀步骤四:修改数据库连接|redis属性配置
启动包: seata–>conf–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。
源码: 根目录–>seata-server–>resources–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。
0以下版本:
启动包: seata–>conf–>file.conf,修改store.db或store.redis相关属性。
源码: 根目录–>seata-server–>resources–>file.conf,修改store.db或store.redis相关属性。
🍀步骤五:启动
码启动: 执行ServerApplication.java的main方法
命令启动:
seata-server.sh -h 127.0.0.1 -p 8091 -m db
0以下版本
源码启动: 执行Server.java的main方法
命令启动:
seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test
命令启动参数:
-h: 注册到注册中心的ip-p: Server rpc 监听端口-m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突-e: 多环境配置参考 Http://seata.io/en-us/docs/ops/multi-configuration-isolation.html
🍀步骤一:添加seata依赖(建议单选)
🍀步骤二:undo_log建表、配置参数(仅AT模式)
🍀步骤三:数据源代理(不支持自动和手动配置并存)
(1)如果使用seata-all:
1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk proxy或者cglib proxy1.0.0: client.support.spring.datasource.autoproxy=true0.9.0: support.spring.datasource.autoproxy=true
如果采用XA模式,@EnableAutoDataSourceProxy(dataSourceProxyMode = "XA")
@Primary@Bean("dataSource")public DataSource dataSource(DataSource druidDataSource) { //AT 代理 二选一 return new DataSourceProxy(druidDataSource); //XA 代理 return new DataSourceProxyXA(druidDataSource)}
(2)如果使用seata-starter
application.yml:
seata: data-source-proxy-mode: XA
application.yml:
seata: enable-auto-data-source-proxy: false
🍀步骤四:初始化GlobalTransactionScanner
手动:
@Bean public GlobalTransactionScanner globalTransactionScanner() { String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name"); String txServiceGroup = this.seataProperties.getTxServiceGroup(); if (StringUtils.isEmpty(txServiceGroup)) { txServiceGroup = applicationName + "-fescar-service-group"; this.seataProperties.setTxServiceGroup(txServiceGroup); } return new GlobalTransactionScanner(applicationName, txServiceGroup); }
自动:
引入seata-spring-boot-starter、spring-cloud-starter-alibaba-seata等jar
🍀步骤五:实现xid跨服务传递
🍀业务使用——注解拦截
全局事务:
@GetMapping(value = "testCommit")@GlobalTransactionalpublic Object testCommit(@RequestParam(name = "id",defaultValue = "1") Integer id, @RequestParam(name = "sum", defaultValue = "1") Integer sum) { Boolean ok = productService.reduceStock(id, sum); if (ok) { LocalDateTime now = LocalDateTime.now(); Orders orders = new Orders(); orders.setCreateTime(now); orders.setProductId(id); orders.setReplaceTime(now); orders.setSum(sum); orderService.save(orders); return "ok"; } else { return "fail"; }}
TCC:
@TwoPhaseBusinessAction(name = "beanName", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)public void insert(@BusinessActionContextParameter(paramName = "params") Map<String, String> params) { logger.info("此处可以预留资源,或者利用tcc的特点,与AT混用,二阶段时利用一阶段在此处存放的消息,通过二阶段发出,比如redis,MQ等操作");}public void commit(BusinessActionContext context) { logger.info("预留资源真正处理,或者发出mq消息和redis入库");}public void rollback(BusinessActionContext context) { logger.info("预留资源释放,或清除一阶段准备让二阶段提交时发出的消息缓存");}
🍀业务使用——切点表达式
全局事务:
@Bean public AspectTransactionalInterceptor aspectTransactionalInterceptor () { return new AspectTransactionalInterceptor(); } @Bean public Advisor txAdviceAdvisor(AspectTransactionalInterceptor aspectTransactionalInterceptor ) { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("配置切点表达式使全局事务拦截器生效"); return new DefaultPointcutAdvisor(pointcut, aspectTransactionalInterceptor); }
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
来源地址:https://blog.csdn.net/qq_42146402/article/details/129819709
--结束END--
本文标题: Spring Cloud Alibaba全家桶(九)——分布式事务组件Seata
本文链接: https://lsjlt.com/news/391707.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0