SpringCloudAlibaba分布式事务解决方案Seata实战与源码分析-上
概述
定义
Spring Cloud Alibaba Seata 官网地址 https://seata.io/zh-cn/ 最新版本1.5.2
Spring Cloud Alibaba Seata 文档地址 https://seata.io/zh-cn/docs/overview/what-is-seata.html
Spring Cloud Alibaba Seata GitHub源码地址 https://github.com/seata/seata
Spring Cloud Alibaba Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
事务概念
数据库事务(简称:事务,Transaction)是指数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务拥有以下四个特性,习惯上被称为ACID特性,也是指数据库事务正确执行的四个基本要素的缩写包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库,必须要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。
- 原子性(Atomicity)
- 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)
- 事务前后数据的完整性必须保持一致。转账 A:1000 B:1000 转 200 事务成功 A:800 B:1200
- 隔离性(Isolation)
- 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,一个事务在执行过程中不能看到其他事务运行过程的中间状态,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离,通过配置事务隔离级别可以避免脏读、重复读等问题。
- 持久性(Durability)
- 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
具有ACID的特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事物间是隔离的,互相完全不影响,而且最终状态是持久落盘的,因此,数据库会从一个明确的状态到另外一个明确的状态,中间的临时状态是不会出现的,如果出现也会及时的自动的修复,因此是强一致的。
本地事务与分布式事务
- 本地事务:简单理解就是整个服务(不是整个项目,而是该微服务)只操作单一数据源如同一服务器同一MySQL数据库。事务仅限于对单一数据库资源的访问控制,架构服务化以后,事务的概念延伸到了服务中。倘若将一个单一的服务操作作为一个事务,那么整个服务操作只能涉及一个单一的数据库资源,这类基于单个服务单一数据库资源访问的事务,被称为本地事务(Local Transaction)。
- 分布式事务:分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。
- 随着互联网的发展,从之前的单一项目逐渐向分布式服务做转换,现如今微服务在各个公司已经普遍存在,而当时的本地事务已经无法满足分布式应用的要求,因此分布式服务之间事务的协调就产生了问题,如果做到多个服务之间事务的操作,能够像本地事务一样遵循ACID原则也成为难题。
- 本质上来说,分布式事务就是为了保证不同数据库的数据一致性;较之基于单一数据库资源访问的本地事务,分布式事务的应用架构更为复杂。在不同的分布式应用架构下,实现一个分布式事务要考虑的问题并不完全一样,比如对多资源的协调、事务的跨服务传播等,实现机制也是复杂多变。
分布式事务理论基础
分布式事务存在两大理论依据: CAP定律和BASE理论;在前面文章《Apache ZooKeeper原理剖析及分布式理论名企高频面试》也有介绍过。BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想既然无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
- Basically Available(基本可用):保证核心业务是可以使用,至于其他业务可以适当降低响应时间甚至是服务降级。假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:
- 响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 1 秒作用返回结果。
- 功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
- Soft state(软状态):相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
- Eventually consistent(最终一致性):系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。
两阶段提交(2PC)
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,P是指准备阶段,C是指提交阶段。
-
准备阶段(Prepare phase):事务管理器给每个参与者发送 Prepare消息,每个数据库参与者在本地执行事务,并写本地的 Undo/Redo日志,此时事务没有提交。
- undo日志是记录修改前的数据,用于数据库回滚。
- Redo日志是记录修改后的数据,用于提交事务写入数据文件。
-
提交阶段(commit phase):如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者发送(Rollback) 消息,如果收到参与者都成功,发送(Commit) 参与者根据事务管理器的指令执行提交或者回滚操作,并释放事务处理过程中使用的资源。
- 执行事务成功提交
- 执行事务失败回滚
三阶段提交(3PC)
3PC 主要是为了解决两阶段提交协议的单点故障问题和缩小参与者阻塞范围。 是二阶段提交(2PC)的改进版本,引入参与节点的超时机制之外,3PC把2PC的准备阶段分成事务询问(该阶段不会阻塞)和事务预提交,则三个阶段分别为 CanCommit、PreCommit、DoCommit
。
- 3PC是2PC的升级版,引入了超时机制,解决了单点故障引起的事务阻塞问题,但是3PC依然不能解决事务一致性的问题,因为在DoCommit阶段,如果由于网络或者超时等原则导致参与者收不到协调者发送过来的 中断事务消息(abort) ,过了这个时间后,参与者会提交事务,本来是应该进行回滚,提交事务后,会导致数据不一致的问题出现
- 2PC虽然在网络故障情况下存在强一致性被破坏的问题,但是故障恢复以后能保证最终一致性,3PC虽然有超时时间,解决了阻塞,提高了可用性,但是牺牲了一致性,如果针对网络波动问题导致数据问题这一点上,2PC是优于3PC的。
安装
版本下载
Seata Server作为分布式事务的TC,也即是事务协调者,也是一个Server服务,需要独立部署。Seata Server官网支持多种方式部署:直接部署,使用 Docker, 使用 Docker-Compose, 使用 Kubernetes, 使用 Helm、高可用部署,官网最新版本为1.5.2。我们这里选择直接部署+高可用,先下载官网提供的二进制包。
# 下载最新版本
wget https://github.com/seata/seata/releases/download/v1.5.2/seata-server-1.5.2.tar.gz
# 解压
tar -xvf seata-server-1.5.2.tar.gz
# 进入目录
cd seata
建议部署结构
官方推荐生产使用的部署结构首先基于Nacos配置中心来管理Seata的配置(事务模式、存储模式如基于DB数据库配置等)、将使用Nacos注册中心将Seata自己注册到Nacos,分布式事务所关联微服务可以通过注册中心感知Seata,实现服务发现和负载均衡等功能,最后将全局协调信息写入数据库,以便多个Seata之间数据共享。
配置数据库
先下载1.5.2的源码 https://github.com/seata/seata/archive/refs/tags/v1.5.2.zip 解压后seata根目录下script\server\db文件下有三类数据库sql脚本。我们选择MySQL的作为数据库因此使用mysql.sql脚本。
创建seata数据库,执行脚本,最新版本1.5.2比之前1.4.x版本很明显多了一张distributed_lock表,原来只有3张
Nacos 配置
为了后面可以结合示例说明,这里使用前面使用simple_ecommerce命令空间,创建seataServer.properties,组为ecom-group
seataServer.properties的配置内容如下,主要是配置db模式及其连接信息
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
service.vgroupMapping.default_tx_group=default
service.default.grouplist=192.168.50.94:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
log.exceptionRate=100
store.mode=db
store.lock.mode=db
store.session.mode=db
store.publicKey=
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.50.95:3308/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
上面seataServer.properties的内容直接从官网复制过来修改,来自在源码根目录下script\config-center\config.txt
Seata配置
在Seata的conf目录下有application.yml和application.example.yml,application.example.yml是配置样例参考,修改application.yml中config和registry为nacos,详细内容如下:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: nacos
nacos:
server-addr: 192.168.50.95:8848
namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
group: ecom-group
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
data-id: seataServer.properties
registry:
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 192.168.50.95:8848
group: ecom-group
namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
cluster: default
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enableCheckAuth: true
retryDeadThreshold: 130000
xaerNotaRetryTimeout: 60000
recovery:
handle-all-session-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 30000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
由于前面配置的数据库为MySQL8的版本,而官方打包在target目录下seata-server.jar中包含的mysql-connector-java-5.1.35.jar驱动
因此这里我们拷贝mysql-connector-java-8.0.28.jar到target目录下,并修改bin/seata-server.sh的启动脚本
# 在图中位置增加
JAVA_OPT="${JAVA_OPT} -Xbootclasspath/a:${BASEDIR}/target/mysql-connector-java-8.0.28.jar"
# 启动seata
sh ./bin/seata-server.sh
成功启动,日志中可以看到Seata启动控制台端口7091和Seata提供服务的端口8091(为控制台端口+1000)
访问http://192.168.5.52:7091/端口,用户名密码为seata/seata(在application.yml中配置),登录成功后页面如下
此外Seata的服务也已经注册到Nacos中
**本人博客网站 **IT小神 www.itxiaoshen.com