Seata--分布式事务
1、Seata 分布式事务基础
1.1事务
事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销。简单地说,事务提供一种“要么什么都不做,要么做全套”机制。
1.2本地事务
本地事物其实可以认为是数据库提供的事务机制。说到数据库事务就不得不说,数据库事务中的四大特性:
A:原子性(Atomicity),一个事务中的所有操作,要么全部完成,要么全部不完成C:一致性(Consistency),在一个事务执行之前和执行之后数据库都必须处于一致性状态I:隔离性(Isolation),在并发环境中,当不同的事务同时操作相同的数据时,事务之间互不影响D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久的保存下来
数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单元中的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚
1.3分布式事务
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
1.4分布式事务的场景
- 单体系统访问多个数据库
一个服务需要调用多个数据库实例完成数据的增删改操作
- 多个微服务访问同一个数据库
多个服务需要调用一个数据库实例完成数据的增删改操作
- 多个微服务访问多个数据库
多个服务需要调用一个数据库实例完成数据的增删改操作
2、分布式事务解决方案
2.1全局事务
全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X/OpenDistributed Transaction Processing Reference Model。它规定了要实现分布式事务,需要三种角色:
- AP: Application应用系统(微服务)
- TM: Transaction Manager事务管理器(全局事务管理)
- RM: Resource Manager资源管理器(数据库)
整个事务分成两个阶段:
阶段一:表决阶段,所有参与者都将本事务执行预提交,并将能否成功的信息反馈发给协调者。9阶段二:执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地执行提交或者回滚。
优点
- 提高了数据一致性的概率,实现成本较低
缺点
- 单点问题:事务协调者宕机
- 同步阻塞:延迟了提交时间,加长了资源阻塞时间
- 数据不一致:提交第二阶段,依然存在commit结果未知的情况,有可能导致数据不一致
2.2可靠消息服务
基于可靠消息服务的方案是通过消息中间件保证上、下游应用数据操作的一致性。假设有A和B两个系统,分别可以处理任务A和任务B。此时存在一个业务流程,需要将任务A和任务B在同一个事务中处理。就可以使用消息中间件来实现这种分布式事务。
第一步:消息由系统A投递到中间件
- .在系统A处理任务A前,首先向消息中间件发送一条消息
- .消息中间件收到后将该条消息持久化,但并不投递。持久化成功后,向A回复一个确认应答
- .系统A收到确认应答后,则可以开始处理任务A
- .任务A处理完成后,向消息中间件发送Commit或者Rollback请求。该请求发送完成后,对系统A而言,该事务的处理过程就结束了
- .如果消息中间件收到Commit,则向B系统投递消息;如果收到Rollback,则直接丢弃消息。但是如果消息中间件收不到Commit和Rollback指令,那么就要依靠”超时询问机制”。
第二步:消息由中间件投递到系统B
- 消息中间件向下游系统投递完消息后便进入阻塞等待状态,下游系统便立即进行任务的处理,任务处理完成后便向消息中间件返回应答。
- 如果消息中间件收到确认应答后便认为该事务处理完毕
- 如果消息中间件在等待确认应答超时之后就会重新投递,直到下游消费者返回消费成功响应为止。一般消息中间件可以设置消息重试的次数和时间间隔,如果最终还是不能成功投递,则需要手工干预。这里之所以使用人工干预,而不是使用让A系统回滚,主要是考虑到整个系统设计的复杂度问题。
基于可靠消息服务的分布式事务,前半部分使用异步,注重性能;后半部分使用同步,注重开发成本。
2.3最大努力通知
最大努力通知也被称为定期校对,其实是对第二种解决方案的进一步优化。它引入了本地消息表来记录错误消息,然后加入失败消息的定期校对功能,来进一步保证消息会被下游系统消费。
第一步:消息由系统A投递到中间件
- .处理业务的同一事务中,向本地消息表中写入一条记录
- .准备专门的消息发送者不断地发送本地消息表中的消息到消息中间件,如果发送失败则重试
第二步:消息由中间件投递到系统B
- .消息中间件收到消息后负责将该消息同步投递给相应的下游系统,并触发下游系统的任务执行
- .当下游系统处理成功后,向消息中间件反馈确认应答,消息中间件便可以将该条消息删除,从而该事务完成
- .对于投递失败的消息,利用重试机制进行重试,对于重试失败的,写入错误消息表
- .消息中间件需要提供失败消息的查询接口,下游系统会定期查询失败消息,并将其消费
这种方式的优缺点:
优点:一种非常经典的实现,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
2.4TCC事务
TCC即为Try Confirm Cancel,它属于补偿型分布式事务。TCC实现分布式事务一共有三个步骤:
- Try:尝试待执行的业务
这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源
- Confirm:确认执行业务
确认执行业务操作,不做任何业务检查,只使用Try阶段预留的业务资源。通常情况下,采用TCC则认为Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引入重试机制或人工处理。
- Cancel:取消待执行的业务
取消Try阶段预留的业务资源。通常情况下,采用TCC则认为Cancel阶段也是一定成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。
TCC两阶段提交与XA两阶段提交的区别是:
XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
TCC事务的优缺点:
优点:把数据库层的二阶段提交上提到了应用层来实现,规避了数据库层的2PC性能低下问题。
缺点:TCC的Try、Confirm和Cancel操作功能需业务提供,开发成本高。
3、Seata介绍
2019年1月,阿里巴巴中间件团队发起了开源项目Fescar(Fast & EaSy Commit AndRollback),其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。后来更名为Seata,意为:Simple Extensible AutonomousTransaction Architecture,是一套分布式事务解决方案。
Seata的设计目标是对业务无侵入,因此从业务无侵入的2PC方案着手,在传统2PC的基础上演进。它把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据库的本地事务。
Seata主要由三个重要组件组成:
- TC:Transaction Coordinator事务协调器,管理全局的分支事务的状态,用于全局性事务的提交和回滚。
- TM:Transaction Manager事务管理器,用于开启、提交或者回滚全局事务。
- RM:Resource Manager资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。
Seata的执行流程如下:
- .A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID
- .A服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖
- .A服务执行分支事务,向数据库做操作
- .A服务开始远程调用B服务,此时XID会在微服务的调用链上传播
- .B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖
- .B服务执行分支事务,向数据库做操作
- .全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚
- .TC协调其管辖之下的所有分支事务,决定是否回滚
Seata实现2PC与传统2PC的差别:
- .架构层次方面,传统2PC方案的RM实际上是在数据库层,RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。
- .两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率。
4、Seata实现分布式事务控制
4.1启动Seata
4.1.1 下载地址:下载seata v0.9.0 https://github.com/seata/seata/releases/tag/v0.9.0
4.1.2修改配置文件
将下载得到的压缩包进行解压,进入conf目录,调整下面的配置文件:
registry.conf
registry{
type="nacos"
nacos{
serverAddr="localhost"
namespace="public"
cluster="default"
}
}
config{
type="nacos"
nacos{
serverAddr="localhost"
namespace="public"
cluster="default"
}
}
将配置导入到nacos
vim nacos-config.txt
service.vgroup_mapping.springcloud-alibaba-impl-member=default
service.vgroup_mapping.springcloud-alibaba-impl-order=default
初始化:
sh nacos-config.sh 127.0.0.1
启动seata服务
sh seata-server.sh -p 9000 -m file
启动后在Nacos的服务列表下面可以看到一个名为serverAddr的服务。
4.2使用Seata实现事务控制" class="reference-link">
4.2使用Seata实现事务控制
4.2.1初始化数据表
在我们所使用的的数据库中加入一张undo_log表,这是Seata记录事务日志要用到的表
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
4.3添加配置
在需要进行分布式控制的微服务中进行下面几项配置:
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
4.4添加配置类
DataSourceProxyConfig:
Seata是通过代理数据源实现事务分支的,所以需要配置io.seata.rm.datasource.DataSourceProxy的Bean,且是@Primary默认的数据源,否则事务不会回滚,无法实现分布式事务
package application.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author: wtl
* @License: (C) Copyright 2020, wtl Corporation Limited.
* @Contact: 1050100468@qq.com
* @Date: 2021/1/4 6:57
* @Version: 1.0
* @Description:
*/
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix="spring.datasource")
public DruidDataSource druidDataSource(){
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
}
还没有评论,来说两句吧...