以下是一个基于 Redis 的 Seata 分布式事务在电商支付场景的真实生产案例,包含架构设计、关键配置、业务流程及优化实践,可直接参考落地。
一、案例背景
某电商平台的 "下单-支付-扣库存" 流程 涉及多服务协同,需保证分布式事务一致性:
- 订单服务:创建订单,状态流转(待支付→已支付)。
- 支付服务:扣减用户账户余额,调用第三方支付渠道(如支付宝)。
- 库存服务:扣减商品库存(超卖防护)。
痛点 :高并发场景下(如促销活动),传统 DB 存储的 Seata 存在性能瓶颈,而 Redis 基于内存存储可支撑更高吞吐量,故采用 Seata + Redis 存储模式 优化。
二、架构设计
1. 整体架构
markdown
客户端层:APP/小程序 → API网关
服务层:订单服务 → 支付服务 → 库存服务(均为 Spring Cloud 微服务)
中间件层:
- 注册中心:Nacos(服务发现)
- 分布式事务:Seata Server 集群(3节点,Redis 存储事务日志)
- 缓存/存储:Redis Cluster(Seata 日志)、MySQL(业务数据)、RabbitMQ(异步通知)
2. 核心组件版本
- Seata Server:1.6.1(支持 Redis 存储模式)
- Redis Cluster:6.2.6(3主3从,开启 AOF+RDB 持久化)
- Spring Cloud:2021.0.5
- Nacos:2.1.2
三、关键配置实现
1. Seata Server 配置(Redis 存储)
file.conf
核心配置(Seata Server 节点):
ini
store {
mode = "redis" # 事务日志存储模式为 Redis
redis {
cluster = "cluster" # Redis 集群模式
serverNodes = "redis-node1:6379,redis-node2:6379,redis-node3:6379" # 主节点地址
password = "Prod@Redis2023" # 生产环境强密码
database = 2 # 独立 DB 索引,避免与业务缓存冲突
maxTotal = 200 # 连接池最大连接数(高并发调大)
minIdle = 20 # 最小空闲连接
maxWait = 3000 # 获取连接的最大等待时间(毫秒)
serializer = "kryo" # 序列化方式(kryo 比 jackson 性能高 30%+)
keyPrefix = "seata:prod:" # 键前缀,便于区分环境
# 超时配置(防止网络阻塞)
connectTimeout = 5000
timeout = 2000
}
}
# 注册中心(Nacos)
registry {
type = "nacos"
nacos {
serverAddr = "nacos1:8848,nacos2:8848"
group = "SEATA_PROD_GROUP"
namespace = "seata-prod" # 隔离命名空间
username = "nacos"
password = "Nacos@2023"
}
}
2. 客户端(微服务)配置
application.yml
(以支付服务为例):
yaml
spring:
application:
name: payment-service
cloud:
alibaba:
seata:
tx-service-group: order_pay_tx_group # 事务组(与 Seata Server 一致)
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: ${spring.cloud.alibaba.seata.tx-service-group}
registry:
type: nacos
nacos:
server-addr: "nacos1:8848,nacos2:8848"
group: "SEATA_PROD_GROUP"
namespace: "seata-prod"
service:
vgroup-mapping:
order_pay_tx_group: "default" # 映射到 Seata 集群
client:
rm:
report-success-enable: false # 成功结果不汇报,减少网络开销
tm:
commit-retry-count: 3 # 提交重试次数
rollback-retry-count: 3 # 回滚重试次数
四、业务流程与 TCC 实现
以 "下单扣库存+支付" 为例,采用 TCC 模式保证事务一致性:
1. 全局事务发起(订单服务)
java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockTCCService stockTCCService; // 库存 TCC 接口
@Autowired
private PaymentTCCService paymentTCCService; // 支付 TCC 接口
@GlobalTransactional(name = "order-create", timeoutMills = 30000) // 全局事务注解
@Override
public String createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务)
String orderId = generateOrderId();
orderMapper.insert(new Order(orderId, orderDTO.getUserId(), orderDTO.getGoodsId(),
orderDTO.getAmount(), OrderStatus.PENDING_PAY));
// 2. 调用库存 TCC 的 Try 方法(预扣库存)
boolean stockResult = stockTCCService.tryDeductStock(
orderDTO.getGoodsId(), orderDTO.getQuantity()
);
if (!stockResult) {
throw new RuntimeException("库存不足");
}
// 3. 调用支付 TCC 的 Try 方法(预扣余额)
boolean payResult = paymentTCCService.tryDeductBalance(
orderDTO.getUserId(), orderDTO.getAmount()
);
if (!payResult) {
throw new RuntimeException("余额不足");
}
return orderId;
}
}
2. 支付服务 TCC 实现(核心)
java
@Service
public class PaymentTCCServiceImpl implements PaymentTCCService {
@Autowired
private AccountMapper accountMapper; // 操作 MySQL 账户表
@Autowired
private StringRedisTemplate redisTemplate; // 业务缓存(非 Seata 日志)
/**
* Try 阶段:预扣余额(冻结金额)
*/
@Override
@TwoPhaseBusinessAction(name = "paymentTcc", commitMethod = "confirm", rollbackMethod = "cancel")
public boolean tryDeductBalance(
BusinessActionContext context,
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount) {
// 1. 幂等性控制(防止重复调用)
String xid = context.getXid();
if (Boolean.TRUE.equals(redisTemplate.hasKey("seata:pay:try:" + xid))) {
return true;
}
// 2. 检查余额并冻结
Account account = accountMapper.selectByUserIdForUpdate(userId); // 悲观锁防并发
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
int rows = accountMapper.freezeAmount(userId, amount); // 冻结金额:balance 不变,frozen 增加
if (rows <= 0) {
throw new RuntimeException("冻结金额失败");
}
// 3. 标记 Try 已执行(幂等记录)
redisTemplate.opsForValue().set("seata:pay:try:" + xid, "1", 24, TimeUnit.HOURS);
return true;
}
/**
* Confirm 阶段:确认扣减(实际扣减余额)
*/
@Override
public boolean confirm(BusinessActionContext context) {
String xid = context.getXid();
String userId = context.getActionContext("userId").toString();
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
// 1. 幂等性控制
if (Boolean.TRUE.equals(redisTemplate.hasKey("seata:pay:confirm:" + xid))) {
return true;
}
// 2. 实际扣减(冻结金额转扣减)
accountMapper.confirmDeduct(userId, amount); // balance 减少,frozen 减少
// 3. 调用第三方支付渠道(异步,不阻塞事务)
asyncCallPaymentChannel(context.getActionContext("orderId").toString(), amount);
// 4. 标记 Confirm 已执行
redisTemplate.opsForValue().set("seata:pay:confirm:" + xid, "1", 24, TimeUnit.HOURS);
return true;
}
/**
* Cancel 阶段:取消扣减(解冻金额)
*/
@Override
public boolean cancel(BusinessActionContext context) {
String xid = context.getXid();
String userId = context.getActionContext("userId").toString();
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
// 1. 幂等性控制
if (Boolean.TRUE.equals(redisTemplate.hasKey("seata:pay:cancel:" + xid))) {
return true;
}
// 2. 解冻金额
accountMapper.unfreezeAmount(userId, amount); // frozen 减少
// 3. 标记 Cancel 已执行
redisTemplate.opsForValue().set("seata:pay:cancel:" + xid, "1", 24, TimeUnit.HOURS);
return true;
}
}
五、生产环境关键优化
1. Redis 集群优化
- 分片与扩缩容:采用 Redis Cluster 3主3从,每个主节点负责 1/3 哈希槽,支持在线扩缩容(应对促销高峰)。
- 持久化策略 :
appendonly yes
(AOF 每秒刷盘)+save 3600 1
(每小时快照),兼顾性能与数据安全。 - 内存淘汰 :
maxmemory-policy allkeys-lru
,优先淘汰最近最少使用的 Seata 历史日志(已完成事务)。
2. Seata 性能调优
- 事务超时控制 :全局事务超时设为 30 秒(
timeoutMills = 30000
),避免长期占用 Redis 资源。 - 异步提交 :非核心场景开启 Seata 异步提交(
client.tm.async-commit-buffer-limit = 10000
),减少阻塞。 - 日志清理 :通过 Redis 定时任务(
KEYS seata:prod:global:*
+EXPIRE
),自动清理 24 小时前的已完成事务日志。
3. 业务代码健壮性
- 幂等设计 :所有 TCC 方法通过 Redis 记录
xid
实现幂等(防止 Seata 重试导致的重复操作)。 - 空回滚防护 :在
cancel
方法中检查Try
阶段是否执行(通过 Redis 标记),避免无资源操作。 - 降级策略:Redis 集群故障时,临时切换 Seata 存储模式为 DB(需提前配置双存储兼容)。
六、监控与告警
1. 监控指标
- Redis 层面 :通过 Prometheus + Redis Exporter 监控
used_memory
、cluster_health
、keyspace_hits/misses
。 - Seata 层面 :集成 Seata 监控模块,监控
global_transaction_count
(全局事务数)、commit_rate
(提交成功率)、rollback_rate
(回滚率)。
2. 关键告警
- Redis 内存使用率 > 80% 时告警(防止 OOM)。
- Seata 事务回滚率 > 5% 时告警(可能存在业务异常)。
- Seata 与 Redis 连接失败次数 > 3 次/分钟时告警(网络或 Redis 故障)。
七、案例效果
- 性能提升:相比 DB 存储模式,Seata 事务处理吞吐量提升 60%+,平均响应时间从 50ms 降至 20ms。
- 高可用保障:Redis 集群支持自动故障转移,Seata Server 集群无单点故障,近半年零数据不一致问题。
- 可扩展性:支持每秒 1000+ 订单的并发场景,促销期间通过临时扩容 Redis 节点即可应对流量峰值。
八、经验总结
- Redis 选型:生产环境必须用集群模式,单节点存在数据丢失风险。
- 序列化选择 :优先用
kryo
而非默认jackson
,尤其大事务场景性能差异明显。 - 幂等性是核心:TCC 各阶段必须实现幂等,否则 Seata 重试机制可能导致数据错乱。
- 监控先行:提前部署 Redis 和 Seata 监控,避免故障后无法追溯问题。