从安装 Seata 开始的分布式事务之旅
- 介绍
- 什么是 Seata?
- 安装 Seata Server
- 下载 Seata Server 发行版
- 配置Seata
- 解压文件
- 配置Seata的yml文件
- 把配置文件config.txt加载到nacos上
- 修改config.txt文件
- 加载到nacos上
- 启动Seata服务
- 正常启动
- 查看启动日志
- 打开控制台页面
- 启动时遇到的坑
- 无法解析${console.user.username}的值
- 无法解析${seata.security.secretKey}的值
- 数据库连接问题
- 在 Spring Boot 项目中集成 Seata
- 工作环境
- cloud、boot、alibaba环境
- 添加依赖
- 配置yml
- 业务代码中集成 Seata
- 使用 `@GlobalTransactional` 注解管理全局事务
- 演示 AT(自动补偿)模式和 TCC(两阶段提交)模式
- 使用 AT(自动补偿)模式
- 使用 TCC(两阶段提交)模式
- 集成Seata踩的坑
- Table 'ddz.undo_log' doesn't exist
- no available service 'null' found, please make sure registry config correct
- 分布式事务未生效
- dynamic-datasource can not find primary datasource
- Communications link failure
- 总结
- 参考资料
介绍
什么是 Seata?
在现代应用程序开发中,分布式系统的应用越来越广泛。然而,随着系统的复杂性增加,处理分布式事务变得愈发困难。这就是 Seata 出现的背景。Seata(Simple Extensible Autonomous Transaction Architecture)是一种开源的分布式事务解决方案,旨在解决分布式系统中的事务一致性和协调性问题。
在传统的单体应用中,通常使用关系型数据库来管理事务,保证数据的一致性。但在分布式系统中,由于涉及多个独立的服务,事务管理变得复杂。分布式事务的要求是:所有涉及的服务要么都成功提交,要么都回滚,以保持数据的一致性。
Seata 提供了两种主要的事务模式:
-
AT 模式(自动补偿模式):在 AT 模式中,Seata会自动补偿事务,无需手动编写补偿逻辑。Seata会将事务的所有操作编排成一个全局的事务,然后执行各个分支的 try 操作,在出现异常时执行相应的补偿操作。
-
TCC 模式(两阶段提交模式):TCC 模式要求开发者手动编写 Try、Confirm 和 Cancel 三个阶段的逻辑。在 TCC 模式中,Seata负责协调全局事务的提交和回滚,而各个分支的 try、confirm 和 cancel 操作则由开发者来实现。
Seata 还提供了可扩展的注册中心和存储支持,使其适用于各种不同的场景。
总的来说,Seata 是一个强大的分布式事务解决方案,可以帮助开发者解决分布式事务的难题,确保分布式系统中数据的一致性和可靠性。通过集成 Seata,开发者可以更加轻松地构建复杂的分布式应用,提升系统的稳定性和性能。
安装 Seata Server
下载 Seata Server 发行版
您可以从 Seata 官方网站下载最新的 Seata Server 发行版,并解压到指定目录;有源码和二进制版本,我们这里选择安装二进制文件下载。我的版本是1.7.0 (2023-07-11,推荐版本)
配置Seata
解压文件
下载下来的是zip文件,解压后是上面文件夹,默认文件夹名字是seata。
配置Seata的yml文件
进入seata/conf
目录下,这里有两个配置文件; 我们需要把application.yml
随意修改一个名字;然后再 application.example.yml
修改成application.yml
作为主要配置文件。
修改 application.yml
文件,我这里使用的nacos作为注册中心,所以需要修改的地方有:
- seata:config:type
- seata:registry:type
- store:mode
- store:session:mode
- store: lock:mode
- store:db 数据库的配置修改成自己的
配置文件:
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
# 这里是主要的配置文件
seata:
config:
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
# 如果在nacos上添加了命名空间,则配置命令空间ID
namespace:
# 配置分组
group: SEATA_GROUP
username:
password:
context-path:
##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:
context-path:
##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
enableParallelRequestHandle: true
recovery:
committing-retry-period: 1000
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-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: db
lock:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: mysql
password: mysql
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 15000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
把配置文件config.txt加载到nacos上
修改config.txt文件
config.txt
文件在seata/script/config-center
目录下;我们需要修改的地方有:
- store.mode=db
- store.lock.mode=db
- store.session.mode=db
- store.db 数据库的配置修改成自己的,和上面yml文件里面的一样
加载到nacos上
进入seata/script/config-center/nacos
目录下执行nacos-config.sh
文件。
sh nacos-config.sh -h 121.37.228.169 -p 8848 -g SEATA_GROUP -t 0af6e97b-a684-4647-b696-7c6d42aecce7 -u nacos -w nacos
- -h: Nacos IP地址
- -p: Nacos端口号
- -g: Group分组名
- -t: 命名空间ID,没有则默认public
- -u: 用户名
- -w:密码
执行完成时候登录我们的Nacos配置管理就能查看到加载好的数据:
启动Seata服务
正常启动
进入seata/bin
目录下,执行命令:
sh seata-server.sh
这里我启动的时候可以会遇到报异常的情况,下面我出了几种我在启动时遇到的一些坑。可以移步到 启动时遇到的坑
查看启动日志
我的是Mac系统所有是open打开日志文件,其他的系统需要根据系统来执行命令,或者直接进入seata/logs
目录下查看start.out
文件。
open /Users/ddz/Downloads/seata/logs/start.out
能看到日志输出地址说明启动成功了。
打开控制台页面
控制台页面,默认账号密码是 seata/seata。
启动时遇到的坑
这里是我在部署Seata中遇到的一些坑,可能和有不同的地方只做为参考。
无法解析${console.user.username}的值
异常信息: Could not resolve placeholder ‘console.user.username’ in value “${console.user.username}”
解决方法:需要把之前修改成其他名称的yml文件中console
下的所有配置信息复制到现在的application.yml
下。这里就是设置我们登录控制台页面的账号密码。
无法解析${seata.security.secretKey}的值
异常信息:Could not resolve placeholder ‘seata.security.secretKey’ in value “${seata.security.secretKey}”
解决方法:需要把之前修改成其他名称的yml文件中seata.security
下的所有配置信息复制到现在的application.yml
下。
数据库连接问题
异常信息:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
在MySQL5.7之前的版本,安全性较低,存在任何用户都可以连接上数据库,所以官方在5.7版本加大了对隐私的保护。并且采用了默认 useSSL = true值防止对数据库的随意修改,到了8.0版本,仍然保留了SSL,并且默认值为 true。
解决方法:在数据库配置url后追加&useSSL=false
;需要检查yml配置文件和Nacos上配置列表中store.db.url
。
在 Spring Boot 项目中集成 Seata
上面我们介绍了如何在本地安装seata服务端,接下来介绍一下我们Spring Boot项目中集成seata;这里我只单纯的用一个demo来介绍,可根据自己的业务逻辑来进行实现。
工作环境
- MySQL 5.7.28
- Maven 3.5.4
- JDK 1.8
- Mybatis 3.4.1
- dynamic 3.4.1
cloud、boot、alibaba环境
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
添加依赖
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 多数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Seata分布式事务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
配置yml
server:
port: 7001
spring:
application:
name: ddz-user
cloud:
nacos:
discovery:
# 服务分组
group: ddz
server-addr: 121.37.228.111:8848
# 必须填命名空间的ID
# namespace: 9ebef975-dcc0-4430-9c63-1c62d8a86d82
datasource:
dynamic:
# 开启seata分布式事务
seata: true
strict: false
primary: master
datasource:
master:
url: jdbc:mysql://121.37.228.111:3306/ddz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
username: ddz
password: ddz2023
local:
url: jdbc:mysql://127.0.0.1:3306/ddz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
username: root
password: ddz2023
# MyBatis Plus配置
mybatis-plus:
# 搜索指定包别名
typeAliasesPackage: com.ddz.**.entity
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*.xml
global-config:
db-config:
id-type: auto
configuration:
# 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别
map-underscore-to-camel-case: true
# Seata 配置
seata:
application-id: seata-server
# 是否启用数据源bean的自动代理
enable-auto-data-source-proxy: false
tx-service-group: default_tx_group # 必须和服务器配置一样
registry:
type: nacos
nacos:
# Nacos 服务地址
server-addr: 121.37.228.111:8848
group: SEATA_GROUP
# namespace: 9ebef975-dcc0-4430-9c63-1c62d8a86d82
application: seata-server # 必须和服务器配置一样
# username:
# password:
cluster: default
config:
type: nacos
nacos:
server-addr: 121.37.228.111:8848
group: SEATA_GROUP
# namespace: 9ebef975-dcc0-4430-9c63-1c62d8a86d82
service:
vgroup-mapping:
default_tx_group: default # 必须和服务器配置一样
disable-global-transaction: false
client:
rm:
# 是否上报成功状态
report-success-enable: true
# 重试次数
report-retry-count: 5
业务代码中集成 Seata
我们根据两个数据源创建两个mapper
类然后再controller
中测试;我这里方便测试就省略了业务层。
使用 @GlobalTransactional
注解管理全局事务
演示 AT(自动补偿)模式和 TCC(两阶段提交)模式
使用 AT(自动补偿)模式
在 AT 模式中,Seata会自动补偿事务,无需手动编写补偿逻辑。首先,我们来演示一个简单的转账场景,将资金从一个账户转移到另一个账户,并保证事务的一致性。
- 添加 @GlobalTransactional 注解
在转账服务的方法上添加 @GlobalTransactional 注解来标记全局事务:
@Service
public class TransferService {
@GlobalTransactional
public void transfer(String fromAccount, String toAccount, double amount) {
// 扣除转出账户金额
deductAmount(fromAccount, amount);
// 增加转入账户金额
addAmount(toAccount, amount);
}
// 实现扣除金额逻辑
// ...
// 实现增加金额逻辑
// ...
}
- 测试 AT 模式
编写测试用例来验证 AT 模式的事务管理:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransfer() {
// 假设从账户 A 转账 100 到账户 B
transferService.transfer("accountA", "accountB", 100.0);
}
}
运行测试用例,观察转账是否成功,并查看日志确认 Seata 是否自动补偿了事务。
使用 TCC(两阶段提交)模式
在 TCC 模式中,我们需要手动编写 Try、Confirm 和 Cancel 三个阶段的逻辑,以确保事务的正确执行。下面我们来演示一个简单的订单创建场景,包括下单、扣减库存和创建订单三个阶段。
- 实现 TCC 接口
创建一个 TCC 接口并实现 Try、Confirm 和 Cancel 三个阶段的逻辑:
public interface OrderTccService {
@GlobalTransactional
boolean createOrder(OrderDTO orderDTO);
@TwoPhaseBusinessAction(name = "orderTccService", commitMethod = "confirmOrder", rollbackMethod = "cancelOrder")
boolean tryCreateOrder(OrderDTO orderDTO);
boolean confirmOrder(OrderDTO orderDTO);
boolean cancelOrder(OrderDTO orderDTO);
}
- 实现 TCC 逻辑
在实现类中编写 TCC 逻辑:
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Override
public boolean tryCreateOrder(OrderDTO orderDTO) {
// 预留库存逻辑
// ...
return true;
}
@Override
public boolean confirmOrder(OrderDTO orderDTO) {
// 确认创建订单逻辑
// ...
return true;
}
@Override
public boolean cancelOrder(OrderDTO orderDTO) {
// 取消创建订单逻辑
// ...
return true;
}
}
- 测试 TCC 模式
编写测试用例来验证 TCC 模式的事务管理:
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderTccServiceTest {
@Autowired
private OrderTccService orderTccService;
@Test
public void testCreateOrder() {
// 创建一个订单
OrderDTO orderDTO = new OrderDTO();
// 设置订单信息
// ...
orderTccService.createOrder(orderDTO);
}
}
运行测试用例,观察订单的创建是否成功,并查看日志确认 TCC 模式的 Try、Confirm 和 Cancel 阶段是否正确执行。
集成Seata踩的坑
这里是我在集成过程中遇到的一些坑,可能和有不同的地方只做为参考。
Table ‘ddz.undo_log’ doesn’t exist
异常信息:process connectionProxy commit error: Table ‘ddz.undo_log’ doesn’t exist。
解决方法:在数据源中切少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;
no available service ‘null’ found, please make sure registry config correct
异常信息:出现这个的原因是我们没有把Seata的config.txt
加载到nacos上。
解决方法:执行命令把配置加载到Nacos配置中心上。参考上面:把配置文件config.txt加载到nacos上
分布式事务未生效
原因:我们使用的dynamic
多数据源,默认是没有开启Seata
分布式事务的。
解决方法:在yml配置文件中的dynamic
下添加seata: true
配置以开启分布式事务。
dynamic-datasource can not find primary datasource
原因:出现这个是数据源配置信息错误,我这里出现这个异常的原因是我自己太不细心导致数据库连接(url)连接编写错了。
解决方法:仔细检查一下datasource
下面数据源的配置。
Communications link failure
原因:这里大部分原因是MySQL需要指明是否进行SSL连接,默认是开启SSL连接的。
解决方法:在数据库连接配置URL后追加&useSSL=false
即可。
总结
随着分布式系统的不断发展,分布式事务领域也会不断进化。在未来,我们可以进一步探索更多的分布式事务模式和解决方案,以满足不同业务场景的需求。同时,Seata 作为一个活跃的开源项目,将会不断推出新的功能和改进,我们可以关注 Seata 社区的更新和贡献自己的力量。
此外,除了 Seata,还有其他一些分布式事务解决方案,例如 TCC-Transaction
、SAGA
、HSTC
等,这些方案也值得我们深入学习和探索。根据业务场景的不同,我们可以选择最合适的方案来解决分布式事务问题。
参考资料
Nacos 官方文档
Seata 官方文档
Spring Boot 官方文档