二、Seata
1、2019年,基于TXC(Taobao Transaction Constructor)和GTS(Global Transaction Service)的技术积累,阿里中间件团队发起了开源项目Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。起初起名为Fescar,后更名为Seata。
(1)、设计初衷
①、对业务无侵入
这里的侵入是指,因为分布式事务这个技术问题的制约,要求应用在业务层面进行设计和改造。这种设计和改造往往会给应用带来很高的研发和维护成本。我们希望把分布式事务问题在中间件这个层次解决掉,不要求应用在业务层面做额外的工作。
②、高性能
引入分布式事务的保障,必然会有额外的开销,引起性能的下降。我们希望把分布式事务引入的性能损耗降到非常低的水平,让应用不因为分布式事务的引入导致业务的可用性受影响。
(2)、组件
①、Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
②、Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
③、Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
(3)、流程
①、TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
②、XID在微服务调用链路的上下文中传播。
③、RM向TC注册分支事务,将其纳入XID对应全局事务的管辖。
④、TM向TC发起针对XID的全局提交或回滚决议。
⑤、TC调度XID下管辖的全部分支事务完成提交或回滚请求。
(4)、Seata方案与XA方案比较
①、架构层次
XA方案的RM实际上是在数据库层,RM本质上就是数据库自身(通过提供支持XA的驱动程序来供应用使用)。
而Seata的RM是以Jar包的形式,作为中间件层部署在应用程序这一端的,不依赖与数据库本身对协议的支持,当然也不需要数据库支持XA协议。
这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。
这个设计,剥离了分布式事务方案对数据库在协议(XA协议)支持上的要求。
②、两阶段提交
a、XA方案无论Phase2的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成才释放。
设想一个正常运行的业务,大概率是90%以上的事务最终应该是成功提交的,我们是否可以在Phase1就将本地事务提交呢?这样90%以上的情况下,可以省去Phase2持锁的时间,整体提高效率。
b、Seata方案
分支事务中数据的本地锁由本地事务管理,在分支事务Phase1结束时释放;同时,随着本地事务结束,连接也得以释放;
分支事务中数据的全局锁在事务协调器侧管理,在决议Phase2全局提交时,全局锁马上可以释放。只有在决议全局回滚的情况下,全局锁才被持有至分支的Phase2结束。
(5)、分支事务提交、回滚
①、阶段1
Seata的JDBC数据源代理通过对业务SQL的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用本地事务的ACID特性,将业务数据的更新和回滚日志的写入在同一个本地事务中提交。这样,可以保证任何提交的业务数据的更新一定有相应的回滚日志存在。基于这样的机制,分支的本地事务便可以在全局事务的Phase1提交,马上释放本地事务锁定的资源。
②、阶段2
a、如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2可以非常快速地完成。
b、如果决议是全局回滚,RM收到协调器发来的回滚请求,通过XID和Branch ID找到相应的回滚日志记录,通过回滚记录生成反向的更新SQL并执行,以完成分支的回滚。
(6)、事务传播机制
XID是一个全局事务的唯一标识,事务传播机制要做的就是把XID在服务调用链路中传递下去,并绑定到服务的事务上下文中,这样,服务链路中的数据库更新操作,就都会向该XID代表的全局事务注册分支,纳入同一个全局事务的管辖。
①、PROPAGATION_REQUIRED:默认支持
②、PROPAGATION_SUPPORTS:默认支持
③、PROPAGATION_MANDATORY:应用通过API来实现
④、PROPAGATION_REQUIRES_NEW:应用通过API来实现
⑤、PROPAGATION_NOT_SUPPORTED:应用通过API来实现
⑥、PROPAGATION_NEVER:应用通过API来实现
⑦、PROPAGATION_NESTED:不支持
(7)、隔离性
①、写隔离
a、一阶段本地事务提交前,需要确保先拿到全局锁。
b、拿不到全局锁,不能提交本地事务。
c、拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
示例:两个全局事务tx1和tx2,分别对a表的m字段进行更新操作,m 的初始值1000。
tx1先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 – 100 = 900。本地事务提交前,先拿到该记录的全局锁,本地提交释放本地锁。tx2后开始,开启本地事务,拿到本地锁,更新操作 m = 900 – 100 = 800。本地事务提交前,尝试拿该记录的全局锁,tx1全局提交前,该记录的全局锁被tx1持有,tx2需要重试等待全局锁。
tx1二阶段全局提交,释放全局锁 。tx2拿到全局锁提交本地事务。
如果tx1的二阶段全局回滚,则tx1需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果tx2仍在等待该数据的全局锁,同时持有本地锁,则tx1的分支回滚会失败。分支的回滚会一直重试,直到tx2的全局锁等锁超时,放弃全局锁并回滚本地事务释放本地锁,tx1的分支回滚最终成功。
因为整个过程全局锁在tx1结束前一直是被tx1持有的,所以不会发生脏写的问题。
②、读隔离
在数据库本地事务隔离级别读已提交(Read Committed)或以上的基础上,Seata(AT 模式)的默认全局隔离级别是读未提交(Read Uncommitted)。
如果应用在特定场景下,必需要求全局的读已提交 ,目前Seata的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请全局锁,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被block住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。
出于总体性能上的考虑,Seata目前的方案并没有对所有SELECT语句都进行代理,仅针对FOR UPDATE 的 SELECT语句。
2、Seata-Server(TC)配置
(1)、配置pom.xml
导入seata相关Jar包
<dependency>
<groupId>${seataGroupId}</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>${seataGroupId}</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
(2)、配置yml
seata:
# 注册中心类型为eureka,默认file,支持file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
registry:
type: eureka
eureka:
application: seata-server
serviceUrl: http://eureka-0.eureka.dev.local:8080/eureka/
weight: 1
instance-id: 192.168.123.123:8091
# 配置中心类型,默认file,支持file、nacos 、apollo、zk、consul、etcd3、custom
config:
type: file
transport:
# tcp udt unix-domain-socket
type: TCP
#NIO NATIVE
server: NIO
# client和server通信心跳检测开关,默认true开启
heartbeat: true
# 客户端事务消息请求是否批量合并发送
enableClientBatchSendRequest: false
# client和server通信编解码方式,默认seata
serialization: seata
# client和server通信数据压缩方式,none、gzip,默认none
compressor: none
store:
# 事务会话信息存储方式,file本地文件(不支持HA),db数据库/redis(支持HA)
mode: db
## database store property
db:
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource: druid
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType: mysql
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://10.110.120.123:4000/common-dev?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true
user: dev-user
password: dev-user
minConn: 5
maxConn: 30
globalTable: global_table
branchTable: branch_table
lockTable: lock_table
queryLimit: 100
maxWait: 5000
## server configuration, only used in server side
server:
port: 8091
host: 195.163.123.123:8091
recovery:
# 二阶段提交未完成状态全局事务重试提交线程间隔时间,默认1000,单位毫秒
committingRetryPeriod: 1000
# 二阶段异步提交状态重试提交线程间隔时间,默认1000,单位毫秒
asynCommittingRetryPeriod: 1000
# 二阶段回滚状态重试回滚线程间隔时间,默认1000,单位毫秒
rollbackingRetryPeriod: 1000
# 超时状态检测重试线程间隔时间,默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
timeoutRetryPeriod: 1000
undo:
# undo保留天数,默认7天
logSaveDays: 7
(3)、配置file.conf
(4)、配置registry.conf
(5)、建数据库
①、global_table:每当有一个全局事务发起后,就会在该表中记录全局事务的ID。
②、branch_table:记录每一个分支事务的ID,分支事务操作的哪个数据库等信息
③、lock_table:用于申请全局锁。
2、Seata-Client(TM、RM)(CPOE项目)配置
(1)、配置pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
(2)、配置yml
seata:
# 是否开启spring-boot自动装配
enabled: true
# 分支事务应用ID
application-id: seata-client
# 事务分组
tx-service-group: tx-service-group
enable-auto-data-source-proxy: true
# 注册中心类型为Eureka,为了支持高可用seata-server可能是集群的,交由eureka管理,那么客户端只需向eureka去发现seata-server即可
registry:
type: eureka
eureka:
application: seata-server
serviceUrl: http://eureka-0.eureka.dev.local:8080/eureka/
service:
# 事务群组,配置项值为TC集群名
vgroupMapping:
tx-service-group: seata-server
(3)、全局事务发起处添加注解@GlobalTransactional
(4)、@GlobalTransactional和@Transactional(rollbackFor = Exception.class)应用
submit()方法中:a、更新数据01;b、更新数据02;c、更新数据03;
①、只用@Transactional
开启本地事务,对本地事务进行支持。如果用了@Transactional则保证a、b、c操作都在同一个本地事务中执行,并且更新时会加行锁,如果本地事务不统一提交,其他SQL不能再更新此条数据。如果不加@Transactional则默认没有事务a、b、c操作分别执行,不会加行锁,其他SQL都可以随便更新。
②、只用@GlobalTransactional
开启全局事务,保证分布式事务。如果只用@GlobalTransactional,那就保证分布式事务,各个分支事务(本地事务)的统一,但是不能保证各个分支事务(本地事务)操作的统一。各个本地操作在无事务的状态下执行操作,不会加锁,别的操作可以随意修改。
③、@GlobalTransactional和@Transactional一起用
@Transactional保证本地事务一致性,@GlobalTransactional保证全局事务的一致性。
今天的文章二、Seata_seata读音分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/64819.html