微服务场景下分布式事务的选型判断
微服务拆分后,一个业务操作跨多个服务的数据一致性保障是架构设计的核心难题。在Spring Boot微服务架构中,分布式事务方案的选择不是非此即彼,而是需要根据业务特征做判断:
强一致性场景(如账户转账、库存扣减):必须用分布式事务保证ACID。Seata的AT模式对业务代码侵入最小,是目前最主流的选择。
最终一致性场景(如订单状态流转、消息通知):可靠消息最终一致性方案更合适,通过RocketMQ事务消息+本地消息表实现。
混合场景:核心链路用Seata AT模式,非核心链路用消息驱动。不要一刀切。
本文聚焦Seata AT模式在Spring Boot微服务中的深度配置与生产调优。
Seata AT模式的事务协调机制解析
Seata AT模式的核心是全局锁机制。事务协调流程分为三个阶段:
一阶段:拦截业务SQL,生成前镜像(before image),执行业务SQL,生成后镜像(after image),生成回滚日志(undo_log),提交本地事务前申请全局锁。
二阶段-提交:异步删除undo_log,释放全局锁。因为一阶段已经提交了本地事务,二阶段提交是异步的,性能开销极低。
二阶段-回滚:根据undo_log中的前镜像反向补偿,释放全局锁。
全局锁的粒度是行级锁,锁的key是表名+主键值。这意味着不同行的数据可以并发操作,同一行的数据会被串行化。理解这一点对性能调优至关重要。
Seata Server的高可用部署配置
生产环境Seata Server必须集群部署。推荐使用Nacos作为注册中心和配置中心:
# seata-server application.yml
server:
port: 8091
seata:
config:
type: nacos
nacos:
server-addr: nacos-cluster:8848
namespace: seata
group: SEATA_GROUP
data-id: seataServer.properties
registry:
type: nacos
nacos:
server-addr: nacos-cluster:8848
namespace: seata
group: SEATA_GROUP
application: seata-server
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql-cluster:3306/seata?rewriteBatchedStatements=true
user: seata
password: ${SEATA_DB_PASSWORD}
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
Seata Server的数据库需要提前创建表结构。全局事务状态表、分支事务表和锁表是核心表,数据量取决于并发事务量。对于高并发场景,建议对global_table的xid和status字段建联合索引,对lock_table的table_name+pk字段建联合索引。
Spring Boot微服务的Seata客户端集成
每个参与分布式事务的微服务都需要集成Seata客户端。依赖和配置如下:
<!-- pom.xml -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.2</version>
</dependency>
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
registry:
type: nacos
nacos:
server-addr: nacos-cluster:8848
namespace: seata
group: SEATA_GROUP
application: seata-server
config:
type: nacos
nacos:
server-addr: nacos-cluster:8848
namespace: seata
group: SEATA_GROUP
data-id: seataClient.properties
数据源代理是AT模式生效的关键。Seata通过代理数据源拦截SQL执行。如果项目中有多个数据源,每个都需要代理:
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
HikariDataSource hikariDataSource = new HikariDataSource();
return new DataSourceProxy(hikariDataSource);
}
}
每个微服务数据库中的undo_log表必须手动创建:
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context',
`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 = utf8mb4 COMMENT ='AT transaction mode undo log';
订单-库存-账户三服务分布式事务实战
以经典的下单扣库存扣余额为例,展示完整的分布式事务配置:
// 订单服务 - 事务发起方
@Service
public class OrderService {
@DubboReference
private InventoryService inventoryService;
@DubboReference
private AccountService accountService;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO orderDTO) {
// 1. 创建订单
Order order = orderMapper.insert(orderDTO);
// 2. 扣减库存(远程调用)
inventoryService.deduct(orderDTO.getProductId(),
orderDTO.getCount());
// 3. 扣减账户余额(远程调用)
accountService.debit(orderDTO.getUserId(),
orderDTO.getAmount());
// 4. 更新订单状态
orderMapper.updateStatus(order.getId(), "PAID");
return order;
}
}
@GlobalTransactional注解标记事务边界。Seata会自动传播xid到远程调用链路。如果步骤2或3抛出异常,步骤1的订单记录会被自动回滚(基于undo_log的前镜像补偿)。
这里有一个关键细节:远程调用的传播机制。Dubbo和Feign都有对应的Seata拦截器,自动在请求头中携带xid。如果使用RestTemplate或其他HTTP客户端,需要手动传播:
// RestTemplate的xid传播拦截器
public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution) throws IOException {
String xid = RootContext.getXID();
if (xid != null) {
request.getHeaders().set(RootContext.KEY_XID, xid);
}
return execution.execute(request, body);
}
}
Seata AT模式的生产调优要点
全局锁超时调优:默认全局锁等待时间是30秒。在高并发场景,如果一个事务持有锁时间较长,其他事务会频繁超时回滚。根据业务RT调整:
# seataClient.properties
seata.client.lock.retry-interval=10
seata.client.lock.retry-times=30
seata.client.lock.retry-policy-branch-rollback-on-conflict=true
undo_log清理:已提交事务的undo_log没有自动清理机制,需要定时任务清理。建议保留7天,防止极端情况下需要手动回滚:
@Scheduled(cron = "0 0 3 * * ?")
public void cleanUndoLog() {
LocalDate expireDate = LocalDate.now().minusDays(7);
undoLogMapper.deleteByLogCreatedBefore(expireDate);
}
写隔离与脏读防护:AT模式默认是读未提交隔离级别。在全局事务提交前,本地事务已经提交,其他事务可以读到未最终确认的数据。如果业务对隔离性要求严格,需要开启全局锁读防护:
# 开启全局锁读防护
seata.client.undo-data-validation=true
seata.client.undo-log-serialization=jackson
Seata Server性能监控:通过Seata Server暴露的Metrics端点监控事务成功率和延迟:
# prometheus配置
- job_name: 'seata'
metrics_path: '/metrics'
static_configs:
- targets: ['seata-server:8091']
# 关键指标:
# seata_transaction_total{status="committed"} 提交数
# seata_transaction_total{status="rollbacked"} 回滚数
# seata_transaction_duration_seconds 事务耗时分布
当回滚率持续超过5%时需要排查业务逻辑,可能是并发冲突热点或事务粒度过大。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-jia-gou-xia-fen-bu-shi-shi-wu-shi-zhan/