-
数据库事务
单个逻辑工作单元执行的多个数据库操作,要么同时成功,要么同时失败,满足ACID特性
A:原子性,事务为原子工作单元,不可分割,要么全部成功,要么全部失败
C:一致性,事务完成时,所有数据都必须保持一致
I:隔离性,并发事务的修改相互隔离
D:持久性,事务成功后,对系统的影响是永久性的 -
分布式事务
服务的拆分及数据库的拆分,每个数据库的事务执行情况只有自己知道,会导致数据库之间的数据不一致问题
分布式事务指事务的参与者、支持事务的服务器、资源服务器及事务管理器分别位于分布式系统的不同节点上
- 分布式事务问题的理论模型
- 分布式事务问题的常见解决方案
- 分布式事务框架Seata
- Seata的安装
- AT模式Dubbo集成Seata
- Spring Cloud Alibaba Seata
- Seata AT模式的实现原理
分布式事务问题的理论模型
- 问题
分布式事务问题即分布式数据一致性问题,即分布式场景中如何保证多个节点数据的一致性 - 原因
存储资源的分布性,多个数据库和不同存储设备的数据一致性(Mysql、Redis) - 避免
尽可能从设计层面去避免分布式事务的问题,因为任何一种解决方案都会增加系统的复杂度
X/Open分布式事务模型
提出了两阶段提交(2PC,Two-Phase-Commit)来保证分布式事务的完整性
-
对应角色
AP:应用程序
RM:资源管理器,例如数据库
TM:全局事务管理器,事务协调者,例如Spring的Transaction Manager -
实现流程
1.配置TM,TM注册RM作为数据源
2.AP从TM管理的RM获取连接
3.AP向TM发起一个全局事务,生成全局事务ID(XID),XID通知各个RM
4.AP操作RM操作数据,每次操作时把XID传递给RM
5.AP结束全局事务,TM通知各个RM全局事务结束
6.根据各个RM的事务执行结果,执行提交或者回滚操作
-
全局事务
在原本的单机事务下,存在跨事务库的可见性问题,导致无法实现多节点事务的全局可控
TM和多个RM之间的事务控制基于XA协议,Oracle、MySQL、DB2都实现了XA接口,都可以作为RM
两阶段提交协议(CP强一致性)
TM实现多个RM事务的管理,涉及两个阶段
- 准备阶段
事务管理器TM通知资源管理器RM准备分支事务,记录事务日志,并告知事务管理器的准备结果 - 提交/回滚阶段
所有的RM在准备阶段都明确返回成功,TM会向所有RM发起事务提交指令完成事务变更
任何一个RM返回失败,TM会向所有RM发送事务回滚指令
缺点
- 同步阻塞
所有RM都是事务阻塞型的,必须有明确的响应才能继续进行下一步 - 过于保守
任何一个节点失败都会回滚 - TM单点故障
如果TM在第二阶段出现故障,所有的RM都会一直处于锁定状态 - “脑裂”导致数据不一致
在第二阶段中,TM发送commit,如果由于网络原因一部分TM未收到请求会导致节点无法提交事务,出现数据不一致性
三阶段提交协议(AP强一致性)
二阶段提交的改进版,利用超时机制解决了同步阻塞问题
三阶段协议具体步骤
- CanCommit询问阶段
TM向RM发送是否可以执行事务请求,该阶段有超时终止机制 - PreCommit准备阶段
TM根据RM反馈结果决定是否继续执行,如果执行则发送PreCommit,RM收到后写redo、undo日志,执行事务操作,但不提交。该阶段任意RM不能返回执行操作结果,则TM会中断事务 - DoCommit提交或回滚阶段
如果第二阶段所有RM返回成功,则TM发起事务提交指令,返回发起事务终止进行回滚
三阶段二阶段不同点
1.增加了CanCommit阶段,可以尽早发现无法执行操作而终止后续行为
2.准备阶段后,TM和RM引入超时机制,一旦超时,会默认成功继续提交事务
三阶段优点
最大优点未基于超时机制来避免资源的永久锁定
CAP定理和BASE理论
二阶段和三阶段为了保证数据的强一致性,降低了可用性
CAP定理
分布式系统中不可能同时满足一致性(C:Consistency)、可用性(A:Availability)、分区容错性(P:Partition Tolerance)三个基本需求。最多只能同时满足两个
- C:数据在多个副本中要保持强一致,分布式数据一致性问题
- A:系统对外提供的服务必须一直处于可用状态,在任何故障下,客户端都能在合理的时间内获得服务端的非错误响应
- P:系统遇到任何网络分区故障,仍然能够正常对外提供服务
网络分区:不同子节点之间出现网络不通的情况,导致整个系统环境呗切分成若干独立的区域
- CAP只能满足CP、AP
原因在于网络通信并不是绝对可靠,会导致系统故障。分布式系统中,网络故障也要保证系统正常对外提供服务,即P是必须的,满足分区容错性
CAP、CA只有在网络绝对可靠的情况下才能达到。如果出现网络分区,为了保证数据的一致性,必须拒绝客户端请求,就无法满足A - AP:放弃强一致性,实现最终一致性,主要选择该方法
- CP:放弃高可用性,实现强一致性。二三阶段提交都采用该方法,会导致用户完成一个操作会等待较长时间
BASE理论
通过牺牲数据的强一致性来获得高可用性,由CAP衍生出BASE
- 特性
1.Basically Available(基本可用):系统故障时,允许损失一部分功能可用性,保证核心功能可用性
2.Soft State(软状态):允许数据存在中间状态,即允许系统中不同节点的数据副本之间的同步存在延时
3.Eventuanlly Consistnet(最终一致性):中间状态的数据在经过一段时间后,会达到最终的数据一致性
BASE不要求数据的强一致性,而是允许数据在一段时间内不一致,但是最终在某个时间点实现一致性
- 举例
用户发起订单支付,不需要等待支付的执行结果,系统返回支付处理中的状态到用户界面。
用户可以从订单列表查看到支付处理结果
系统在第三方支付处理成功后,再更新订单支付状态
订单支付状态和第三方状态存在短期不一致,但用户获得了更好的产品体验
分布式事务问题的常见解决方案
数据的一致性问题可以有CP、AP两种方案
CP强一致性在数据库性能和系统处理能力上存在瓶颈
更多采用遵循BASE理论来实现的柔性事务模型,其特性为:基本可用、柔性状态
TCC补偿型方案(两阶段提交)
TCC(Tcy-Confirm-Cancel)
- Try:对数据进行校验或资源的预留
- Confirm:确认真正执行的任务,只操作Try阶段预留的资源
- Cancel:取消执行,释放Try阶段预留的资源
TCC是二阶段提交思想
- 第一阶段:Try进行准备工作
- 第二阶段:Confirm/Cancel表示Try阶段操作的确认和回滚
TCC举例
理财APP中,用户通过账户余额购买一个理财产品
-
涉及操作
1.用户账户服务:对用户账户余额进行扣减
2.理财产品服务:对指定理财产品可申购金额进行扣减 -
Try、Confirm、Cancel方法
1.用户账户服务:Try对申购金额冻结,Comfirm把Try冻结资金进行实际扣减,Cancel把Try方法冻结资金进行解冻
2.理财产品服务:Try对申购的部分额度进行冻结,Comfirm把Try冻结的额度进行实际扣减,Cancel把Try冻结额度进行解冻 -
主业务方法
分别调用者两个服务对外提供的处理方法,当战虎、理财服务做实际业务处理时,会先调用Try方法做资源预留,如果正常则TCC事务协调器调用Comfirm方法对预留资源进行实际应用,否则调用各个服务的Cancel方法进行回滚保证数据一致性 -
异常处理
服务宕机出现异常时,TCC事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态。TCC事务协调器会根据操作日志来进行重试。
TCC服务支持接口调用失败发起重试,所以TCC暴露的接口都需要满足幂等性
基于可靠性消息的最终一致性方案
基于可靠性消息的最终一致性是常见的分布式数据一致性解决方案
利用消息中间件(Kafka、RocketMQ、RabbitMQ)的可靠性机制来实现数据一致性的投递
RocketMQ为例
- 执行逻辑
1.生产者发送一个事务消息到消息队列上,消息队列记录消息数据,此时消费者无法消费该消息
2.生产者执行具体业务逻辑,完成本地事务的操作
3.生产者根据本地事务执行结果发送消息给消息队列,如果本地执行成功发送Commit消息,表示先前发送的消息可以呗消费。如果本地执行失败则消息队列服务器把先前发送的消息删除
4.如果生产者一直未给消息队列服务器发送确认,消息队列服务器会定时主动回查生产者获取本地事务的执行结果,根据结果来决定该消息是否投递给消费者
5.消息队列消息经生产者确认之后,消费者才可以消费,消费者消费完成之后发送确认标识给消息队列服务器,标识消息投递成功 - 事务控制
RocketMQ事务消息模型中,事务是由生产者来完成的,消费者不需要考虑。如果消费者没有消费该消息,消息队列服务器会重复投递,确保生产者本地数据和消费者本地数据在消息队列的机制下达到最终一致 - 核心
事务回查。
服务之间的远程通信有成功、失败、未知状态。服务提供者提供一个查询接口向外部传输操作的执行状态,消费者调用该接口得知之前操作的结果并进行相应的处理
最大努力通知型
简单的柔性事务解决方案
- 适用场景
对数据一致性要求不高的常见,例如支付宝支付结果通知
- 处理逻辑
1.商户创建一个支付订单,调用支付宝接口发起支付请求
2.支付宝唤醒支付页面,完成支付操作,同时为商户创建一个支付交易,根据用户的支付结果记录支付状态
3.支付完成后触发一个回调通知给商户,商户根据通知修改本地支付订单状态,并返回一个处理状态给支付宝
4.理想情况下,该订单会在通知完成后在支付宝和商户之间达到最终一致性。如果由于网络原因,导致通知失败,商户消息消费端没有返回给支付宝成功码,会以衰减重试机制继续触发,直到达到最大通知次数
5.达到上限后,支付宝提供了交易查询接口,根据订单号查询支付状态,再更新商户的支付订单状态,可通过定时器或人工对账触发 - 总结
商户端(消费者)如果没有返回一个消息确认时,支付宝(提供者)会不断地进行重试,直到收到一个消息确认或者达到最大重试次数
分布式事务框架Seata
官网地址: http://seata.io/zh-cn/
阿里巴巴的开源项目
开源的分布式事务解决方案,在微服务架构下提供高性能和简单易用的分布式事务服务。提供了AT、TCC、Saga和XA事务模式
- TC和TM作为Seata客户端与业务系统集成
- TM作为Seata服务端独立部署
AT模式(与XA一样是二阶段提交事务模型)
Seata主推的分布式事务解决方案,基于XA演进的分布式模式
AT模式下,每个数据库资源被当作一个RM,在业务层面通过JDBC标准的接口访问RM时,Seata会对所有请求进行拦截。每个本地事务进行提交时,RM都会向TC注册一个分支事务
执行流程
- TM向TC注册全局事务,并生成全局唯一的XID
- RM向TC注册分支事务,并将其纳入该XID对应的全局事务范围
- RM向TC汇报资源的准备状态
- TC汇总所有事务参与者的执行状态,决定分布式事务是全部回滚还是提交
- TC通知所有RM提交/回滚事务
TCC模式
官网有详细说明,前文也说过直接贴图
Saga模式
长事务解决方案,在没有两阶段提交的情况下如何解决分布式事务问题
- 核心思想
把一个业务流程中的长事务拆分为多个本地短事务,业务流程中的每个参与者都提交真实的提交给该本地短事务,当其中一个参与者事务执行失败,则通过补偿机制补偿前面以及成功的参与者 - 事务模型
每个Ti都有对应的补偿动作Ci用于撤销Ti造成的数据变更结果 - Sage与TCC比较
1.少了Try预留动作
2.每一个Ti操作都真实地影响到数据库 - 官网介绍优劣势
Saga的实现方式
- 案例场景
电商的下单流程是典型的长事务场景:订单创建、商品库存扣减、钱包支付、积分赠送等 - 事务拆分
将长事务拆分成多个本地短事务,每个服务的本地事务按照执行顺序逐一提交,一旦某一个服务事务出现异常则采用补偿方式逐一撤回,涉及Saga的协调模式:事件/编排式、命令/协同式 - Saga的协调模式
事件/编排式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与者中,通过交换事件的方式进行沟通
命令/协同式:把Saga的决策和执行顺序逻辑集中在一个Saga控制类中,以命令/回复的方式与每项服务进行通信,高速其应该执行哪些操作
事件/编排式具体实现逻辑案例
//TODO
命令/协同式具体实现逻辑案例
//TODO
Seata的安装
- github源码编译 https://github.com/seata/seata
- 直接下载编译好的文件 http://seata.io/zh-cn/blog/download.html
- 采坑
默认不支持Mysql8版本,需要更改成MySQL8版本的驱动连接类加个cj即 driverClassName = "com.mysql.cj.jdbc.Driver"
1.4.1版本的MySQL8的驱动jar包是自带的,不需要导入 - 启动参数
Usage: sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for
windows) [options]
Options:
--host, -h
The ip to register to registry center.
--port, -p
The port to listen.
Default: 8091
--storeMode, -m
log store mode : file, db
--serverNode, -n
server node id, such as 1, 2, 3.it will be generated according to the
snowflake by default
--seataEnv, -e
The name used for multi-configuration isolation.
--help
- -h:指定在注册中心的IP地址
- -p:指定服务端启动端口
- -m:事务日志存储方式,默认为file,支持db
- -n:指定seate-server节点ID,默认为1
- -e:指定seata-server运行环境,如dev、test等,服务启动会使用registry-dev.conf类似的配置文件,类似于Spring的profile机制
file存储模式
Server端存储模式有file和db两种模式,file模式无须改动,直接启动即可
file存储模式为单机模式,全局事务会话持久化在本地文件中,性能较高
db存储模式
高可用模式,全局事务会话信息通过db共享,性能相对差一些
使用db
- 建表
1.使用db共享需先创建表结构在源码的script\server\db目录下,编译好的zip文件没有打包建表语句,有点坑!!
2.创建好表格
- 更改配置文件
启动包: seata-->conf-->file.conf,修改store.mode="db或者redis"
源码: 根目录-->seata-server-->resources-->file.conf,修改store.mode="db或者redis" - 修改数据库连接
启动包: seata-->conf-->file.conf,修改store.db或store.redis相关属性。
源码: 根目录-->seata-server-->resources-->file.conf,修改store.db或store.redis相关属性。 - 启动seata-server
源码启动: 执行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
服务端启动,默认为8091
Seata服务端配置中心说明
conf文件夹下有2个配置文件为registry.conf和file.conf
registry.conf配置说明
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
- registry
配置Seata服务注册的地址,支持绝大部分注册中心,通过type指定注册中心类型,再在具体的配置中心配置好即可
默认为file即不依赖于注册中心,快速集成Seata,但不具备注册中心的动态发现和动态配置 - config
- 配置Seata服务端的配置文件地址,指定配置信息加载位置,支持远程配置中心读取和本地文件读取
默认为file,加载file.conf文件中的配置信息
file.conf配置说明
存储Seata服务端本地配置信息。包含transport、server、metrics分别为协议配置、服务端配置、监控
transport {
# tcp, unix-domain-socket
type = "TCP"
#NIO, NATIVE
server = "NIO"
#enable heartbeat client和server通信心跳开关
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = false
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThreadPrefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata" #client和server通信编解码方式
compressor = "none" #client和server通信数据压缩方式(none、gzip,默认为none)
}
## transaction log store, only used in server side 事务日志存储配置
store {
## store mode: file、db
mode = "file"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## 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://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
minConn = 5
maxConn = 30
globalTable = "global_table" #db模式全局事务表名
branchTable = "branch_table" #db模式分支事务表名
lockTable = "lock_table" #db模式全局锁表名
queryLimit = 100 #db模式查询全局事务一次的最大条数
}
}
## server configuration, only used in server side server配置
server {
recovery {
#schedule committing retry period in milliseconds 两阶段提交未完成状态全局事务重试提交线程间隔时间
committingRetryPeriod = 1000
#schedule asyn committing retry period in milliseconds 两阶段异步提交状态重试提交线程间隔时间
asynCommittingRetryPeriod = 1000
#schedule rollbacking retry period in milliseconds 两阶段回滚状态重试回滚线程间隔时间
rollbackingRetryPeriod = 1000
#schedule timeout retry period in milliseconds 超时状态检测重试线程间隔时间
timeoutRetryPeriod = 1000
}
undo {
logSaveDays = 7 #undo保留天数
#schedule delete expired undo_log in milliseconds #undo清理线程间隔时间ms
logDeletePeriod = 86400000
}
#check auth
enableCheckAuth = true
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
maxCommitRetryTimeout = "-1"
maxRollbackRetryTimeout = "-1"
rollbackRetryTimeoutUnlockEnable = false
}
## metrics configuration, only used in server side metrics设置
metrics {
enabled = false #是否启用metrics
registryType = "compact" #指标注册器类型
# multi exporters use comma divided
exporterList = "prometheus" #指标结果Measurement数据输出器列表
exporterPrometheusPort = 9898 #prometheus输出器Client端口号
}
这些参数官网有详细说明,不细说
从配置中心加载配置
把配置信息储存到Nacos上
- 将配置上传到Nacos
只有GitHub源码目录下才有上传脚本文件,其中:
client:存放客户端的SQL脚本,参数配置
config-center:各个配置中心参数导入脚本,config.txt包含通用参数文件
server:服务端数据库脚本及各个容器配置
执行脚本
shell:
bash
sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
Parameter Description:
-h: host, the default value is localhost.
-p: port, the default value is 8848.
-g: Configure grouping, the default value is 'SEATA_GROUP'.
-t: Tenant information, corresponding to the namespace ID field of Nacos, the default value is ''.
-u: username, nacos 1.2.0+ on permission control, the default value is ''.
-w: password, nacos 1.2.0+ on permission control, the default value is ''.
成功后Nacos配置中心会增加配置
Seata服务端修改配置加载位置
修改registry.conf文件下的registry和config配置为nacos相关参数即可
AT模式Dubbo集成Seata
//TODO
Spring Cloud Alibaba Seata
//TODO
Seata AT模式的实现原理
//TODO
Comments | 0 条评论