目录
一、分布式事务基础
(一)事务
(二)本地事务
(三)分布式事务
二、Seata概述
1.Seata 的架构包含:
2.其工作原理为:
3.如果需要在 Spring Boot 应用中使用 Seata 进行分布式事务管理,主要步骤为:
1、下载seata并解压
2、修改配置文件
3、创建数据库
4、初始化seata在nacos的配置
5、启动seata服务
6、Seata实现分布式事务控制案例:
官网:https://seata.io/
事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销。简单地说,事务提供一种“要么什么都不做,要么做全套”机制。
本地事物其实可以认为是数据库提供的事务机制。说到数据库事务就不得不说,数据库事务中的四大特性: A:原子性(Atomicity),一个事务中的所有操作,要么全部完成,要么全部不完成 C:一致性(Consistency),在一个事务执行之前和执行之后数据库都必须处于一致性状态 I:隔离性(Isolation),在并发环境中,当不同的事务同时操作相同的数据时,事务之间互不影响 D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久的保存下来 数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单元中 的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。 本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
Seata官方文档
Seata 是一款开源的分布式事务解决方案,它支持 AT、TCC、SAGA 等事务模式。在微服务架构下提供了分布式事务服务,解决了强一致性的业务处理问题。
TC(Transaction Coordinator):事务协调器,维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(Transaction Manager):控制分支事务,与 TC 交互以控制分支事务的提交、回滚等。
RM(Resource Manager):控制分支事务涉及的资源,与 TM 交互以控制分支事务。
应用开始全局事务,通过调用 TC 的 begin() 方法开启一个全局事务。
应用调用多个 TM 的 begin() 方法开启分支事务。TC 统一控制这些分支事务。
分支事务可以操作多个 RM 的资源,RM 参与者需要实现回滚和提交方法。
当应用结束分支事务并决定提交或回滚全局事务时,它会通知 TC。TC 将指示所有的 TM 提交或回滚相应的分支事务。
TM 将指示 RM 执行实际的提交或回滚操作。
操作完成后,TC、TM 和 RM 会相互确认这些操作,以确保全局事务的最终一致性。
1)下载
下载地址:Release v1.5.2 · seata/seata · GitHub
下载的版本参考:springcloudalibaba-seata版本参考
2)解压
seata/conf/application.yml 进行修改
参考官网配置:[seata/script at master · seata/seata · GitHub]
修改 seata\conf\application.yml 的内容主要有以下三部分:
application.yml
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: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username:
password:
##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: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
username:
password:
##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
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 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
store:
# support: file 、 db 、 redis
mode: db
#session:
# mode: file
#lock:
# mode: file
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: root
password: 123456
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
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
创建上面配置中的 seata 数据库并根据解压路径 seata\script\server\db\mysql.sql 文件创建表
1)进入到上面下载好的seata的解压后的seata\script\config-center\nacos目录中,
执行nacos-config.sh文件,进入当前层级的cmd执行下面带参数的命令,
就会将seata\script\config-center\config.txt文件中的配置全部写入到127.0.0.1:8848的nacos配置中心中。
启动参数说明如下:
执行成功后可以打开Nacos的控制台,在配置列表中,可以看到初始化了很多Group为SEATA_GROUP的配置。
2)适当将seata\script\config-center\config.txt文件的默认配置删掉再去执行nacos-config.sh文件,否则配置中心的配置太多了,我自己的最终config.txt文件如下:
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
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
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
两种启动方式:
windows中双击seata\bin\ seata-server.bat 启动
使用命令启动如下;
cd bin
seata-server.bat -p 9000 -m file
启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr 的服务。
案例解析:一个user模块,一个product模块,测试user通过feign调用product中的方法
第一步:在各个微服务中添加seata依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
第二步:在各个微服务连接的数据库中添加undo_log表
CREATE TABLE `undo_log`
(
`id` BIGiNT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGiNT(20) NOT NULL,
`xid` VARcHAR(100) NOT NULL,
`context` VARcHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` iNT(11) NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
`ext` VARcHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8;
第三步:配置配置事务分组,配置seata注册中心和配置中心
spring:
application:
name: product-server
datasource:
url: jdbc:mysql://localhost:3306/product?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 以下的是每个微服务都要添加的配置
alibaba:
seata:
tx-service-group: default_tx_group
# 配置seata注册中心和配置中心
seata:
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
第四步:在被调用微服务里,编写方法
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductMapper productMapper;
@PostMapping("/get1")
public Integer update(@RequestBody Product product){
int i = productMapper.updateNumByPId(product);
return i;
}
}
第五步:在调用者微服务里写feign接口,调用其他微服务的方法
@FeignClient(value = "product-server", path = "/product")
public interface ProductFeign {
@PostMapping("/get1")
void updateNumByPId(@RequestBody Product product);
}
第六步:在调用者微服务的serviceImpl,编写方法,在方法上加 @GlobalTransactional 注解 开启事务
@Service
public class UserServiceImpl extends ServiceImpl
implements UserService{
@Autowired
private UserMapper userMapper;
@Autowired
private ProductFeign productFeign;
@Override
@GlobalTransactional
public void updateNumByPId(@RequestBody User user) {
Product product = new Product();
product.setPid(1);
product.setNum(2000);
productFeign.updateNumByPId(product);
int a = 1 / 0;
userMapper.updateById(user);
}
}
第七步:controller测试(service层的除零那行打断点Debug测试)
@RestController
@RefreshScope // 在需要动态读取配置的类上添加此注解就可以(动态配置刷新)
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// Seata实现分布式事务控制
@PostMapping("/testSeata")
public void testSeata(@RequestBody User user){
userService.updateNumByPId(user);
}
}
结果:在没有出现除0异常的时候数据被正常修改,出现异常后不会继续往下执行,而是将上面的操作进行回滚。