微服务架构中,跨服务的业务操作需要分布式事务保障数据一致性。Spring Boot框架在单体应用中依赖本地事务即可满足需求,但拆分为微服务后,订单服务、库存服务、账户服务各自维护独立数据库,本地事务无法覆盖跨库操作。Seata作为阿里开源的分布式事务解决方案,通过AT模式实现了对业务代码零侵入的分布式事务管理,是后端开发中服务治理的重要组件。
Seata分布式事务架构与AT模式原理
Seata的AT(Automatic Transaction)模式包含三个核心角色:TC(Transaction Coordinator)事务协调者、TM(Transaction Manager)事务管理器、RM(Resource Manager)资源管理器。TC是独立部署的服务端,TM和RM是嵌入应用中的客户端。
AT模式的工作流程分为两个阶段。第一阶段,各分支事务执行业务SQL并提交本地事务,Seata自动记录数据快照(before image和after image)到undo_log表,同时向TC注册分支事务。第二阶段,若全局事务决议提交,TC异步删除undo_log;若决议回滚,TC通知各RM根据undo_log生成反向SQL恢复数据。
这种设计使得业务代码无需感知分布式事务的存在,只需通过@GlobalTransactional注解标注全局事务入口即可。消息中间件在最终一致性场景中可以与Seata形成互补。
Seata服务端部署与Nacos注册中心配置
Seata Server(TC)部署使用Docker方式,基于Nacos作为注册中心和配置中心:
# docker-compose.yml
version: '3.8'
services:
seata-server:
image: seataio/seata-server:2.0.0
container_name: seata-server
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_PORT=8091
- STORE_MODE=db
- SEATA_IP=192.168.1.100
volumes:
- ./seata-config/application.yml:/seata-server/resources/application.yml
depends_on:
- nacos
- seata-mysql
seata-mysql:
image: mysql:8.0
container_name: seata-mysql
environment:
MYSQL_ROOT_PASSWORD: seata123
MYSQL_DATABASE: seata
ports:
- "3307:3306"
volumes:
- ./seata-mysql-init:/docker-entrypoint-initdb.d
Seata Server的application.yml配置数据库存储模式:
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
store:
mode: db
db:
datasource: druid
db-type: mysql
url: jdbc:mysql://seata-mysql:3306/seata?useUnicode=true
user: root
password: seata123
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
query-limit: 100
微服务客户端集成与数据源代理配置
订单服务、库存服务、账户服务三个微服务都需要集成Seata客户端。以订单服务为例,Maven依赖配置:
<dependencies>
<!-- Seata Spring Boot Starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Seata与Nacos集成 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
application.yml配置Seata客户端:
seata:
enabled: true
application-id: order-service
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 192.168.1.100:8091
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
datasource:
proxy-enabled: true
data-source-proxy-mode: AT
每个业务数据库需要创建undo_log表,Seata通过该表记录数据变更前后的快照:
-- 在每个业务数据库中执行
CREATE TABLE `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚信息',
`log_status` INT NOT NULL COMMENT '状态',
`log_created` DATETIME NOT NULL COMMENT '创建时间',
`log_modified` DATETIME NOT NULL COMMENT '修改时间',
`ext` VARCHAR(100) DEFAULT NULL COMMENT '扩展信息',
PRIMARY KEY (`branch_id`),
KEY `idx_log_created` (`log_created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Seata AT模式undo_log表';
高并发场景下的分布式事务实战编码
以电商下单场景为例,订单服务创建订单后调用库存服务和账户服务。使用@GlobalTransactional注解开启全局事务:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StorageFeignClient storageFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
@GlobalTransactional(name = "createOrder", timeoutMills = 60000,
rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
log.info("====== 开始创建订单, XID: {} ======",
RootContext.getXID());
// 1. 创建订单(本地事务)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setCount(dto.getCount());
order.setMoney(dto.getMoney());
order.setStatus(0); // 待支付
orderMapper.insert(order);
log.info("订单创建成功, orderId={}", order.getId());
// 2. 扣减库存(远程调用)
storageFeignClient.deduct(dto.getProductId(), dto.getCount());
log.info("库存扣减成功");
// 3. 扣减账户余额(远程调用)
accountFeignClient.debit(dto.getUserId(), dto.getMoney());
log.info("账户余额扣减成功");
// 4. 修改订单状态
order.setStatus(1); // 已支付
orderMapper.updateById(order);
log.info("订单状态更新成功");
return order;
}
}
库存服务和账户服务通过Feign调用,被调方不需要添加@GlobalTransactional,Seata通过RPC header自动传递XID。库存服务代码:
@RestController
@RequestMapping("/storage")
public class StorageController {
@Autowired
private StorageService storageService;
@PostMapping("/deduct")
public Result<Void> deduct(@RequestParam Long productId,
@RequestParam Integer count) {
storageService.deduct(productId, count);
return Result.success();
}
}
@Service
public class StorageService {
@Autowired
private StorageMapper storageMapper;
@Transactional(rollbackFor = Exception.class)
public void deduct(Long productId, Integer count) {
Storage storage = storageMapper.selectByProductId(productId);
if (storage == null) {
throw new RuntimeException("商品不存在");
}
if (storage.getResidue() < count) {
throw new RuntimeException("库存不足");
}
storage.setUsed(storage.getUsed() + count);
storage.setResidue(storage.getResidue() - count);
storageMapper.updateById(storage);
}
}
当账户余额不足抛出异常时,全局事务回滚,Seata根据undo_log自动恢复订单状态和库存数据。API接口规范方面,建议在Feign接口中统一使用Result包装类,便于异常信息的传递和解析。
分布式事务监控与服务治理实践
Seata提供事务日志查询接口,可通过控制台监控全局事务执行情况。高并发设计需要关注几个配置项:
# Seata客户端高并发优化配置
seata:
client:
rm:
lock:
retry-interval: 30
retry-times: 10
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
transport:
type: TCP
server: NIO
heartbeat: true
enable-client-batch-send-request: true
业务中台建设时,Seata全局锁可能成为性能瓶颈。对于读多写少的场景,可通过@GlobalLock + SELECT FOR UPDATE替代AT模式,减少undo_log的写入开销。服务治理层面,业务中台建设应将Seata事务超时时间与下游服务超时配置对齐,避免全局事务超时回滚但分支事务仍在执行的竞态问题。
生产环境部署建议:TC集群至少3节点保证高可用,使用MySQL存储模式替代文件模式避免单点故障,定期清理已完成事务的undo_log和global_table记录防止表膨胀。通过Nacos动态配置调整重试策略,实现不停机的事务参数调优。微服务架构中,Seata与Spring Boot框架的集成需要确保数据源代理正确生效,当使用Druid等连接池时,需检查代理顺序是否覆盖了所有事务操作。Java/Go实战中,Seata也提供Go语言客户端,支持跨语言微服务架构的分布式事务协调。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-ji-cheng-seata-shi-xian-wei-fu-wu-fen-bu-shi-shi/