springCloudAlibaba之分布式事务组件—seata
Seata
- Sea学习
- 分布式事务
- Seata
- 二阶段提交协议
- AT模式
- TCC模式
- Seata服务搭建
- Seata Server(事务协调者TC)环境搭建
- seata服务搭建-db数据源
- seata服务搭建-nacos
- 启动seata服务
- 分布式事务代码搭建-client端搭建
- 接入微服务应用
Sea学习
事务:事务是访问数据库并更新数据库中各项数据的一个程序执行单元。在关系数据库中,一个事务由一组或多组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。例如更新商品的接口,这就是一个事务,事务是对程序而言的。
- 原子性:事务是一个不可分割的工作单元,事务中包括的诸多操作要么都做要么都不做。
- 一致性:保持数据的一致
- 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别,来保证事务之间的隔离性:读未提交、读已提交、可重复读、串行化(事务串行执行,不会产生并发问题)
- 持久性:一个事务执行并提交后,就持久到数据库
本地事务:大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务。本地事务的ACID特性由数据库直接支持。
分布式事务
两种分布式事务场景:1、同一服务不同数据库;2、同一数据库不同服务;此时都会产生分布式事务的问题。此时用本地数据库事务是无法支持分布式事务的。此时用Seata处理分布式事务问题
Seata
seata的三大角色
在seata的架构中,一共有三个角色:
- TC:事务协调者
- TM:事务管理器
- RM:资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务的状态,并驱动分支事务提交或回滚。其中,TC为单独部署的server服务端,TM和RM为嵌入到应用中的client客户端。
二阶段提交协议
常见的分布式事务解决方案:
- seata阿里分布式事务框架
- 消息队列
- saga
- XA
他们都有一个共同点,都是遵循二阶段协议(2PC)。两阶段是指完成整个分布式事务,划分成两个步骤完成。
- 1、准备阶段
- 在这个阶段,事务的协调者会向所有涉及的数据库或者资源管理器发送准备请求。
- 每个数据库或者资源管理器收到准备请求后,会执行相应的操作,并且记录准备状态。如果准备成功,就向协调者发送准备就绪的ask。
- 协调者等待所有参与者都发送准备就绪的信号,如果有任何一个参与者未能准备就绪或者出现了错误,协调者将会发送回滚请求给所有参与者。
- 提交阶段
- 如果所有参与者都准备就绪,协调者会向他们发送提交请求。
- 参与者接收到提交请求后,会正式提交事务,并且释放相关资源。
- 如果有任何一个参与者未能接收到请求或者出现了错误,协调者将会发送回滚请求给所有参与者。
- 图例
- 第一阶段
- 第二阶段
就像是军训一样,准备阶段:教官让所有同学都立正站好,所有同学都得对齐站好,谁没有站好,全部同学都得重新立正,直到所有同学都准备好,然后,训练阶段:同学们都站一排齐步走,只要有一个没走齐的就重新开始;(👴已经尽力去解释了,我相信即使没有学过计算机的应该也知道这玩意是什么了,嘿嘿);看着是不是毫无压力,
AT模式
一阶段
befrore image:将要更新的数据查询出来。afer image:事务执行后,将执行完的数据查出来
二阶段
二阶段提交
因为一阶段已经完成了提交动作,所以二阶段如果没有异常,则直接删除before image、after image、行锁
二阶段回滚
根据before image拿到逆向sql回滚数据,回滚完成后删除befor image、after image、行锁
TCC模式
Seata服务搭建
Seata分TC、TM、RM三个角色,其中TC为单独服务端部署,TM和RM由业务系统引入依赖来进行集成。
Seata Server(事务协调者TC)环境搭建
seata服务搭建-db数据源
- 下载安装包
2.2.5对应的seata包时1.3.0,下载地址:https://github.com/apache/incubator-seata/releases?page=2
- 修改server端存储模式
- 打开config/file.conf
- 修改mode=“db”
- 修改数据库连接信息
- 创建数据库
- 新建表:可以去seata提供的资源信息中下载
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://192.168.184.1:3306/seata"
user = "root"
password = "123456"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
- 建表sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
seata服务搭建-nacos
db存储模式+nacos(注册&配置中心)部署
步骤5:配置Nacos注册中心,负责事务参与者(微服务)和TC通信
使用DB+Nacos的方式部署高可用集群模式
问题:seata为何需要注册中心和配置中心?
注册中心是事务参与者需要与事务协调者进行通信。配置中心是,如果不配置seata会使用默认的配置:script/config-center/config.txt文件。使用的配置中心的目的是所有TC都使用nacos配置中心中的配置,方便统一管理
- 将Seata server注册到nacos,修改confi目录下的registry.conf配置
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
}
- 将配置注册到nacos
- 修改confi.txt,还需要将config.txt的数据源改为db,然后通过nacos/nacos-config.sh,注册到nacos
store.db.url=jdbc:mysql://192.168.184.1:3306/seata?useUnicode=true store.db.user=root store.db.password=123456
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1: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=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.184.1:3306/seata?useUnicode=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.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.database=0
store.redis.password=null
store.redis.queryLimit=100
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
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.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
- 注册配置到nacos
打开\script\config-center
acos,运行nacos-config.sh
linux端运行:如果是本地:sh nacos-config.sh,如果nacos在远程:sh nacos-config.sh -h 192.168.184.15 -p 8848
- 执行后
可以看到配置已经注册到了nacos
启动seata服务
sh seata-server.sh -h 192.168.184.15 -p 8091
运行seata服务:sh seata-server.sh -h 192.168.184.15 -p 8091,默认端口号8091
此时可以看到seata服务已经注册到了nacos
分布式事务代码搭建-client端搭建
声明式事务实现(@GlobalTransactional)
接入微服务应用
业务场景
用户下单,整个业务逻辑由三个微服务构成:
- 订单服务:根据采购需求创建订单。
- 库存服务:对给定的商品扣除库存数量;
- 启动seata server端,seata server使用nacos作为配置中心和注册中心(上一步已完成)
- 配置微服务整合seata
- 前置工作:新建两个数据库用来测试
在两个库创建两个表
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int NOT NULL AUTO_INCREMENT,
`product_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`total_amount` int NULL DEFAULT NULL,
`status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
DROP TABLE IF EXISTS `stock_tpl`;
CREATE TABLE `stock_tpl` (
`id` int NOT NULL AUTO_INCREMENT,
`product_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`count` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
- 测试项目架构图
具体项目见码云:
https://gitee.com/www_zzq_com/springcloud-alibaba_study - 第一步:添加pom依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
- 第二步:各微服务对应的数据库中添加undo_log表
undo_log表作用:回滚数据使用
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
- 第三步:配置事务的组
config.txt中配置的分组和客户端必须一一对应
server:
port: 8083
spring:
application:
name: order-seata-service
cloud:
nacos:
server-addr: 192.168.184.15:8848
username: nacos
password: nacos
discovery:
namespace: public
alibaba:
seata:
tx-service-group: shanghai #配置事务分组
- 第四步
配置文件中进行配置与seata服务进行交互
#seata配置
seata:
#seata注册中心
registry:
#配置seata的注册中心,告诉seata client怎么去访问seata server
type: nacos
nacos:
server-addr: 192.168.184.15:8848 #seata server所在的nacos服务地址
application: seata-server #seata server的服务名
username: nacos
password: nacos
#seata配置中心
config:
type: nacos
nacos:
server-addr: 192.168.184.15:8848 #seata server所在的nacos服务地址
username: nacos
password: nacos
- 第五步:使用,在业务方法上添加@GlobalTransactional注解
@GlobalTransactional
public OrderTbl create(OrderTbl orderTbl) {
//插入能否成功?
orderMapper.insertOrder(orderTbl);
//扣减库存,能否成功?
String s = stockService.detectStock(orderTbl.getProductId());
int a=1/0;
return orderTbl;
}