2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase).
2是指两个阶段,P是指准备阶段,C是指提交阶段。
举例:张三和李四好久不见,老友约起聚餐,饭店老板要求先买单,才能出票。这时张三和李四分别抱怨近况不如
意,囊中羞涩,都不愿意请客,这时只能AA。只有张三和李四都付款,老板才能出票安排就餐。但由于张三和李四 都是铁公鸡,形成了尴尬的一幕:
准备阶段:老板要求张三付款,张三付款。老板要求李四付款,李四付款。 提交阶段:老板出票,两人拿票纷纷落座就餐。
例子中形成了一个事务,若张三或李四其中一人拒绝付款,或钱不够,店老板都不会给出票,并且会把已收款退 回。
整个事务过程由事务管理器和参与者组成,店老板就是事务管理器,张三、李四就是事务参与者,事务管理器负责
决策整个分布式事务的提交和回滚,事务参与者负责自己本地事务的提交和回滚。
在计算机中部分关系数据库如Oracle、MySQL支持两阶段提交协议,如下图:
- 准备阶段(Prepare phase):事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事 务,并写本地的Undo/Redo日志,此时事务没有提交。
(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数 据文件)- 提交阶段(commit phase):如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者 发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操
作,并释放事务处理过程中使用的锁资源。注意:必须在最后阶段释放锁资源。 下图展示了2PC的两个阶段,分成功和失败两个情况说明: 成功情况:
这里就不讲解了,传统xa就是案例上将的
prepare阶段事务没提交,需要由事务管理器来确认事务是否提交,这样导致传统2PC会一直持有资源锁,性能差
是由阿里中间件团队发起的开源项目 Fescar,后更名为Seata,它是一个是开源的分布式事务框架。
传统2PC的问题在Seata中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作
在应用层的中间件。主要优点是性能较好,且不长时间占用连接资源,它以高效并且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案
Seata把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务
达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据库的本地事务,下 图是全局事务与分支事务的关系图:
Seata实现2PC与传统2PC的差别: 架构层次方面,传统2PC方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身,通过 XA协议实现,而 Seata的 RM 是以jar包的形式作为中间件层部署在应用程序这一侧的。
两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成
才释放。而Seata的做法是在Phase1 就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率。
本示例通过Seata中间件实现分布式事务,模拟三个账户的转账交易过程。
两个账户在三个不同的银行(张三在bank1、李四在bank2),bank1和bank2是两个个微服务。交易过程是,张三 给李四转账指定金额。
交互流程如下: 1、请求bank1进行转账,传入转账金额。 2、bank1减少转账金额,调用bank2,传入转账金额。
首先要下载事务协调器: https://github.com/seata/seata/releases/download/v0.7.1/seata-server-0.7.1.zip
解压启动: seata-server.bat -p 8888 -m file
注:其中8888为服务端口号;file为启动模式,这里指seata服务将采用文件的方式存储信息。
如上图出现“Server started…”的字样则表示启动成功。
创建mysql
两个库 bank1和bank2 都创建account_info 和undo_log表
CREATE TABLE `account_info` (
`id` bigint(20) DEFAULT NULL COMMENT 'id',
`account_name` varchar(100) DEFAULT NULL COMMENT '户主姓名',
`account_no` varchar(100) DEFAULT NULL COMMENT '户主id',
`account_password` varchar(100) DEFAULT NULL COMMENT '帐户密码',
`account_balance` double DEFAULT NULL COMMENT '帐户余额'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户表'
CREATE TABLE `undo_log` (
`id` bigint(20) DEFAULT NULL COMMENT 'id',
`branch_id` varchar(100) DEFAULT NULL COMMENT '分支id',
`xid` varchar(100) DEFAULT NULL COMMENT '事务id',
`context` varchar(100) DEFAULT NULL COMMENT '内容',
`rollback_info` double DEFAULT NULL COMMENT '回滚信息',
`log_status` double DEFAULT NULL COMMENT '日志状态',
`log_created` double DEFAULT NULL COMMENT '日志创建',
`log_modified` double DEFAULT NULL COMMENT '日志修改',
`ext` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='undo log表'
Hoxton.SR5
SpringBoot使用2.3.1.RELEASE
这里就提示以下代码的作用以及关键处如何配置,源代码地址: https://github.com/cyf0477/seata-parent
1.在bank1和bank2里分别加入seata依赖
2.在resource下加入registry.conf,这个conf需要在之前下载的seata服务器资源里的conf目录下copy
这里面需要关注两个文件,一个是register.conf,一个是file.conf ,这里就先讲讲这个register.conf和file.conf的作用,
registry {
#注册中心选用的方式,默认是file,可以使用nacos ,eureka上面注释列举的所有方式都支持
#注意,如果这里注册中心原则使用file方式,那么就必须使用file.conf来配置file的一些配置,而且file模式不支持高可用
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file" 只能建立一个seata server
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:1001/eureka" #这里我用的eureka,那么只需要修改eureka就可以
application = "default" #seata注册到eureka的名称
weight = "1" #权重
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
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 {
//conf只有下面列举的才需要修改 如果使用的eureka 下面配置无用
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
spring:
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group #seata分组
seata:
service:
grouplist:
default: ubuntu1804.wsl:8091 #seata服务地址 可以是多个 key的值我查看源码里没有要求指定什么名字,这里就随意了,如果seata集群,可以写多个服务地址
bank1 代表案例中的张三,先说一下数据源配置,让seata代理数据源,io.seata.rm.datasource.DataSourceProxy
,这样seata才可能完全管理事务状态
需要在主事物分支开启@GlobalTransactional
注解,在其他子事物分子是不需要这个注解
此时 seata at教程就完毕了 到这里有人可能有点混淆AT和XA AT是在seata里的概念等同于XA 都是两阶段提交解决方案
tcc解决方案使用hmily下章节讲解
http://seata.io/zh-cn/docs/overview/what-is-seata.html
可以关注下博主的公众号,实时推送解决方案!