Spring Boot 3 高并发事务与分布式事务企业级完整解决方案
写这个因为,是有个接口没做事务兜底机制。
从单体高并发 → 分布式高并发 · 全链路异常兜底 · 企业级生产就绪
文档说明
- 适用版本:Spring Boot 3.2+,Java 17+,JPA / MyBatis / R2DBC 均支持
- 目标读者:后端架构师、高级开发工程师、SRE
- 核心价值 :覆盖 单体高并发优化 → 分布式事务选型 → 异常兜底 → 可观测性 → 容灾对账 全生命周期
- 企业级标准:满足金融、电商、支付等强一致性与高可用场景
一、整体演进路径与选型原则
| 架构阶段 | 并发规模 | 数据一致性要求 | 推荐方案 | 是否需分布式事务 |
|---|---|---|---|---|
| 单体应用(高并发) | 1k~10k TPS | 强一致(本地 ACID) | 本地事务 + 乐观锁/分段锁 + 异步解耦 | ❌ 否 |
| 微服务初期(最终一致) | 10k+ TPS | 最终一致(秒级容忍) | Outbox 模式 + MQ + 幂等消费 | ✅ 轻量级 |
| 强一致场景(金融/支付) | 高并发 + 强一致 | 强一致(毫秒级) | Seata TCC / Saga | ✅ 强一致性 |
| 超大规模(跨境/多云) | 100k+ TPS | 最终一致 + 对账兜底 | Event Sourcing + CDC + 补偿平台 | ✅ 最终一致 |
✅ 企业共识:
- 90% 场景用"最终一致性 + 对账"
- 仅关键资金链路用 TCC
- 绝不使用 XA(性能差、运维难)
二、单体高并发事务优化方案(企业级)
2.1 核心问题
- 数据库行锁/间隙锁竞争
- 事务持有时间过长导致连接池耗尽
- 死锁频发(尤其 MySQL InnoDB)
- CPU 飙升、TPS 下降
2.2 企业级解决方案
✅ 方案 A:细粒度事务 + 异步解耦(推荐)
java
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
// 主流程:极简事务(<50ms)
@Transactional(timeout = 10)
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
order.setStatus("CREATED");
order = orderRepo.save(); // 快速提交
// 发布领域事件(异步处理非核心逻辑)
eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), request.getItems()));
return order;
}
}
// 异步监听器(独立事务)
@Component
@RequiredArgsConstructor
public class InventoryEventListener {
private final InventoryService inventoryService;
@Transactional
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
try {
inventoryService.reduceStock(event.orderId(), event.items());
} catch (Exception e) {
// 记录失败,由补偿任务重试
compensationService.recordFailure("INVENTORY_REDUCE", event.orderId(), e);
throw e; // 触发重试机制
}
}
}
优势 :主流程快、锁持有时间短、系统吞吐提升 3~5 倍
注意 :@EventListener默认同步,需配置TaskExecutor实现异步
✅ 方案 B:乐观锁(高并发更新)
java
@Entity
public class Account {
@Version
private Long version; // JPA 乐观锁字段
private BigDecimal balance;
}
@Service
@Transactional
public class AccountService {
public void transfer(Long fromId, Long toId, BigDecimal amount) {
int retry = 0;
while (retry < 3) {
try {
Account from = accountRepo.findById(fromId).orElseThrow();
Account to = accountRepo.findById(toId).orElseThrow();
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepo.save(from);
accountRepo.save(to);
return; // 成功退出
} catch (OptimisticLockException e) {
retry++;
if (retry >= 3) throw new ServiceException("并发冲突,请重试", e);
Thread.sleep(10 * retry); // 指数退避
}
}
}
}
适用 :余额变动、积分增减等低冲突场景
不适用:秒杀库存(高频冲突)
✅ 方案 C:分段锁 + 库存预热(秒杀场景)
java
@Component
public class SeckillInventoryService {
private static final int SEGMENT_COUNT = 16;
private final Object[] locks = new Object[SEGMENT_COUNT];
private final Map<String, AtomicInteger[]> inventorySegments = new ConcurrentHashMap<>();
public SeckillInventoryService() {
for (int i = 0; i < SEGMENT_COUNT; i++) {
locks[i] = new Object();
}
}
public boolean trySeckill(String skuId, Long userId) {
int segment = Math.abs(userId.hashCode()) % SEGMENT_COUNT;
synchronized (locks[segment]) {
AtomicInteger[] segments = inventorySegments.computeIfAbsent(skuId, k ->
IntStream.range(0, SEGMENT_COUNT).mapToObj(i -> new AtomicInteger(getTotalStock(k) / SEGMENT_COUNT)).toArray(AtomicInteger[]::new)
);
if (segments[segment].get() > 0) {
segments[segment].decrementAndGet();
return true;
}
}
return false;
}
// 定时同步到 DB(每秒一次)
@Scheduled(fixedRate = 1000)
public void syncToDatabase() {
// 批量更新 DB 库存
}
}
效果:将全局锁 → 16 个局部锁,并发能力提升 10 倍+
三、分布式高并发事务方案(企业级)
3.1 方案选型矩阵
| 方案 | 一致性 | 性能 | 开发成本 | 运维成本 | 适用场景 |
|---|---|---|---|---|---|
| Outbox + MQ(最终一致) | 最终一致 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | 电商下单、社交互动 |
| Seata TCC | 强一致 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 支付转账、积分兑换 |
| Saga(编排式) | 最终一致 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 订单履约、审批流 |
| XA | 强一致 | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ | 遗留系统(禁用) |
企业推荐:
- 默认选择 Outbox + MQ
- 仅当业务无法容忍任何不一致时用 TCC
3.2 方案一:Outbox 模式 + MQ(最终一致性 · 企业首选)
架构图
- 本地事务 2. 定时任务 3. 异步投递 4. 幂等消费 5. 成功 6. 失败 7. 人工补偿 8. 每日跑批 订单服务
B
RabbitMQ/Kafka
库存服务
扣库存
ACK
死信队列
补偿管理平台
对账系统
核对订单 vs 库存
关键实现
(1)Outbox 消息表(与业务同库)
sql
CREATE TABLE outbox_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
event_type VARCHAR(100) NOT NULL,
payload JSON NOT NULL,
business_key VARCHAR(100) NOT NULL, -- 如 order_id
status ENUM('PENDING', 'SENT') DEFAULT 'PENDING',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
sent_at DATETIME NULL,
INDEX idx_status_created (status, created_at),
UNIQUE KEY uk_biz_event (business_key, event_type)
);
(2)可靠消息发送(带重试)
java
@Service
@RequiredArgsConstructor
public class OutboxMessageService {
private final OutboxMessageRepository outboxRepo;
private final MqProducer mqProducer;
@Scheduled(fixedDelay = 2000)
@Transactional
public void sendPendingMessages() {
List<OutboxMessage> messages = outboxRepo.findPendingBefore(Instant.now().minusSeconds(10));
for (OutboxMessage msg : messages) {
try {
mqProducer.send(msg.getEventType(), msg.getPayload());
msg.markAsSent();
outboxRepo.save(msg);
} catch (Exception e) {
log.warn("消息发送失败,稍后重试: {}", msg.getId(), e);
// 不抛异常,避免事务回滚
}
}
}
// 业务方法中保存消息
public void saveMessage(String eventType, String payload, String businessKey) {
outboxRepo.save(new OutboxMessage(eventType, payload, businessKey));
}
}
(3)幂等消费(双保险)
java
@RabbitListener(queues = "inventory.queue")
public void consume(InventoryEvent event) {
String idempotentKey = event.getOrderId() + ":DECREASE";
// 1. Redis 快速去重
if (redis.hasKey("IDEMPOTENT:" + idempotentKey)) {
return;
}
// 2. DB 唯一索引兜底
try {
idempotentLogService.log(idempotentKey); // 唯一索引 INSERT
inventoryService.decrease(event.getItems());
} catch (DuplicateKeyException e) {
log.info("重复消费,跳过: {}", idempotentKey);
return;
} catch (Exception e) {
// 系统异常:MQ 自动重试(最多5次)
throw new AmqpRejectAndDontRequeueException(e);
}
}
(4)兜底:补偿平台 + 对账
-
补偿平台:提供 UI 查看失败事件、手动重试、跳过、数据修正
-
对账任务
:
java@Scheduled(cron = "0 0 2 * * ?") // 每日凌晨2点 public void dailyReconciliation() { List<Order> paidOrders = orderRepo.findPaidYesterday(); for (Order order : paidOrders) { if (!inventoryService.isAllocated(order.getId())) { alarmService.send("库存未分配", order.getId()); // 自动触发补偿 or 人工介入 } } }
3.3 方案二:Seata TCC(强一致性 · 金融级)
架构图
[Client]
│
▼
[Order Service] → @GlobalTransactional
│
├──→ [Inventory Service] → @LocalTCC (Try/Confirm/Cancel)
└──→ [Account Service] → @LocalTCC (Try/Confirm/Cancel)
│
▼
[Seata Server (TC)] ← 协调全局事务
关键代码
(1)Seata 配置(application.yml)
yaml
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
registry:
type: nacos
nacos:
server-addr: ${NACOS_SERVER}
config:
type: nacos
(2)TCC 接口实现
java
@LocalTCC
public interface InventoryTccService {
@TwoPhaseBusinessAction(name = "decreaseInventory", commitMethod = "confirm", rollbackMethod = "cancel")
boolean prepareDecrease(BusinessActionContext ctx, Long orderId, List<Item> items);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
@Component
public class InventoryTccServiceImpl implements InventoryTccService {
@Override
public boolean prepareDecrease(BusinessActionContext ctx, Long orderId, List<Item> items) {
// Try: 冻结库存(必须幂等)
return inventoryRepo.freezeIfNotExists(orderId, items);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
try {
inventoryRepo.confirmFrozen(ctx.getActionContext("orderId"));
return true;
} catch (Exception e) {
log.error("Confirm 失败,需人工介入!", e);
alarmService.sendCritical("TCC Confirm 失败", ctx.getXid());
return false; // 不会自动重试!
}
}
@Override
public boolean cancel(BusinessActionContext ctx) {
try {
inventoryRepo.releaseFrozen(ctx.getActionContext("orderId"));
return true;
} catch (Exception e) {
// Cancel 失败是严重事故!
alarmService.sendCritical("TCC Cancel 失败", ctx.getXid());
return false;
}
}
}
(3)全局事务调用
java
@Service
public class OrderService {
@GlobalTransactional(timeoutMills = 30000)
public void createOrderWithTcc(OrderRequest request) {
orderRepo.create(request); // 本地 RM
inventoryService.prepareDecrease(request.getItems()); // 远程 TCC
accountService.prepareFreeze(request.getAmount()); // 远程 TCC
}
}
TCC 注意事项:
prepare必须幂等confirm/cancel失败需告警 + 人工处理- 全局事务日志(undo_log)需定期归档
第四章:事务异常兜底与企业级容灾方案(完整重写)
目标 :确保在任何异常场景下,系统具备自动恢复能力 、人工干预通道 和数据最终一致性保障
适用范围 :Spring Boot 3 单体高并发 & 分布式微服务架构
核心理念 :"事务可能失败,但业务不能中断"
4.1 异常分类与响应策略(企业级标准)
4.1.1 异常类型定义
| 异常大类 | 具体场景 | 是否可自动恢复 | 事务影响 |
|---|---|---|---|
| 业务异常 | 余额不足、库存不足、参数校验失败 | ❌ 不可恢复(需用户修正) | 应立即回滚,不触发重试 |
| 系统异常 | NPE、数据库连接失败、序列化错误 | ✅ 可恢复(重试后可能成功) | 应回滚 + 自动重试 |
| 基础设施异常 | 网络超时、MQ 不可用、DB 主从切换 | ✅ 可恢复(依赖外部恢复) | 需本地持久化 + 异步重试 |
| 服务不可用 | 下游服务宕机、 | ✅ 可恢复(服务恢复后重试) | 需消息队列 + 死信机制 |
| 数据不一致 | 跨服务状态不同步(如订单已付但库存未扣) | ⚠️ 部分可自动修复 | 需对账 + 补偿 |
📌 企业原则:
- 业务异常 ≠ 系统异常:前者快速失败,后者必须重试
- 绝不静默吞异常:所有异常必须记录 traceId 并分级告警
4.2 单体应用异常兜底方案
4.2.1 核心问题
- 事务内异常导致回滚,但调用方不知情
- 异步任务失败无追踪
- 重试无上限导致雪崩
4.2.2 企业级解决方案
✅ 方案:事务边界清晰 + 异步任务可靠执行
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final TaskReliableExecutor taskExecutor; // 可靠异步执行器
@Transactional(rollbackFor = Exception.class)
public Order createOrder(OrderRequest request) {
// 1. 核心事务(快速提交)
Order order = new Order(request);
order.setStatus("CREATED");
order = orderRepo.save();
// 2. 提交后立即触发异步任务(不在事务内!)
taskExecutor.submitReliableTask(
"ORDER_PROCESS",
order.getId(),
() -> processOrderAsync(order.getId())
);
return order;
}
// 独立事务方法(由可靠执行器调用)
@Transactional(rollbackFor = Exception.class)
public void processOrderAsync(Long orderId) {
try {
inventoryService.reduceStock(orderId);
paymentService.charge(orderId);
orderRepo.updateStatus(orderId, "PAID");
} catch (InsufficientStockException e) {
// 业务异常:标记订单失败,不重试
orderRepo.updateStatus(orderId, "FAILED", "库存不足");
throw new BusinessException(e.getMessage());
} catch (Exception e) {
// 系统异常:抛出,由可靠执行器重试
log.error("[orderId={}] 处理失败,将重试", orderId, e);
throw e;
}
}
}
🔧 可靠异步执行器实现(带重试+持久化)
java
@Component
public class TaskReliableExecutor {
private final AsyncTaskRepository taskRepo;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
public void submitReliableTask(String taskType, String businessKey, Runnable task) {
// 1. 持久化任务到 DB(与主事务同库)
AsyncTask asyncTask = new AsyncTask(taskType, businessKey, 0);
taskRepo.save(asyncTask);
// 2. 立即执行(或延迟执行)
executeTask(asyncTask);
}
private void executeTask(AsyncTask task) {
try {
// 执行具体业务(通过反射或策略模式)
taskStrategy.execute(task.getTaskType(), task.getBusinessKey());
// 成功:删除任务
taskRepo.deleteById(task.getId());
} catch (BusinessException e) {
// 业务异常:标记失败,不再重试
taskRepo.markFailed(task.getId(), e.getMessage());
} catch (Exception e) {
// 系统异常:重试(最多5次,指数退避)
int retryCount = task.getRetryCount() + 1;
if (retryCount <= 5) {
long delay = (long) Math.pow(2, retryCount) * 1000; // 2s, 4s, 8s...
taskRepo.incrementRetry(task.getId());
scheduler.schedule(() -> executeTask(task), delay, TimeUnit.MILLISECONDS);
} else {
// 超过重试次数:进入死信表,告警
taskRepo.moveToDeadLetter(task.getId(), e.toString());
alarmService.send("异步任务失败", task.toString());
}
}
}
}
优势:
- 主流程无阻塞
- 异常可追踪、可重试、可人工干预
- 与主业务同库,避免分布式事务
4.3 分布式事务异常兜底方案
4.3.1 最终一致性(Outbox + MQ)兜底体系
四层防护机制:
| 防护层 | 组件 | 功能 | 企业级要求 |
|---|---|---|---|
| 第一层:发送可靠 | Outbox 表 + 定时任务 | 确保消息不丢失 | 消息与业务同事务 |
| 第二层:消费幂等 | Redis + DB 唯一索引 | 防重复消费 | 双保险去重 |
| 第三层:失败重试 | MQ 死信队列(DLQ) | 自动重试 + 隔离失败消息 | 重试策略可配置 |
| 第四层:人工兜底 | 补偿管理平台 | 查看/重试/跳过/修正 | 提供 Web UI |
关键代码增强:死信队列处理
java
// RabbitMQ 死信队列配置
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable("inventory.dlq").build();
}
@RabbitListener(queues = "inventory.dlq")
public void handleDeadLetter(Message message) {
String payload = new String(message.getBody());
String orderId = extractOrderId(payload);
// 记录到补偿平台
compensationService.recordFailure(
"INVENTORY_REDUCE",
orderId,
new String(message.getBody()),
message.getMessageProperties().getRedelivered() ? "重试失败" : "首次失败"
);
// 发送企业微信/钉钉告警
alarmService.sendCritical("库存扣减死信", "订单: " + orderId);
}
4.3.2 TCC 模式兜底体系
TCC 异常处理黄金法则:
"Try 可重试,Confirm/CANCEL 必须尽最大努力成功"
兜底措施:
-
Cancel 失败告警
java@Override public boolean cancel(BusinessActionContext ctx) { try { inventoryRepo.releaseFrozen(ctx.getActionContext("orderId")); return true; } catch (Exception e) { // 立即触发 P0 级告警 alarmService.sendP0("TCC Cancel 失败", ctx.getXid(), e); // 尝试备用方案(如调用 Admin API 手动释放) adminApi.releaseFrozenInventory(ctx.getActionContext("orderId")); return false; // Seata 会记录日志 } } -
Confirm 失败人工介入
- Seata 控制台提供 "悬挂事务" 列表
- 运维可手动 Commit / Rollback
-
全局事务日志归档
undo_log表每日归档至冷存储- 保留 180 天用于审计
4.4 企业级容灾四件套
4.4.1 幂等性(所有入口统一治理)
java
// 通用幂等注解(支持 SpEL)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String key() default "#requestId"; // 默认取 requestId
long expireSeconds() default 3600;
}
// AOP 实现(Redis + 本地缓存二级)
@Aspect
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object handle(ProceedingJoinPoint jp, Idempotent idempotent) {
String key = parseSpel(idempotent.key(), jp.getArgs());
String redisKey = "IDEMPOTENT:" + key;
// 1. 本地缓存快速判断(Caffeine)
if (localCache.getIfPresent(redisKey) != null) {
throw new DuplicateRequestException();
}
// 2. Redis 分布式锁(SET NX EX)
Boolean acquired = redis.setIfAbsent(redisKey, "1", Duration.ofSeconds(idempotent.expireSeconds()));
if (Boolean.FALSE.equals(acquired)) {
throw new DuplicateRequestException();
}
try {
Object result = jp.proceed();
localCache.put(redisKey, result); // 缓存结果(可选)
return result;
} finally {
// 不主动删除,靠 TTL 自动过期(防重试期间误删)
}
}
}
4.4.2 补偿管理平台(人工兜底)
-
功能清单:
- 查看失败事件列表(按服务/时间/类型过滤)
- 一键重试 / 批量重试
- 跳过(标记为成功)
- 数据修正(直接调用 Admin API)
- 导出 CSV 用于对账
-
技术实现:
sqlCREATE TABLE compensation_task ( id BIGINT PRIMARY KEY, service_name VARCHAR(100), event_type VARCHAR(100), business_key VARCHAR(100), payload JSON, status ENUM('FAILED', 'RETRIED', 'SKIPPED'), error_message TEXT, created_at DATETIME, updated_at DATETIME );
4.4.3 对账系统(数据一致性最后防线)
-
对账类型:
对账类型 频率 说明 实时对账 事件驱动 关键操作后立即核对(如支付成功后查库存) 准实时对账 5分钟一次 扫描最近变更数据 离线对账 每日凌晨 全量比对(T+1) -
对签示例(离线):
java@Scheduled(cron = "0 0 2 * * ?") public void dailyReconciliation() { List<ReconcileItem> mismatches = reconciliationService.findMismatches( LocalDate.now().minusDays(1) ); for (ReconcileItem item : mismatches) { if (item.canAutoFix()) { autoFixService.fix(item); } else { // 生成工单,通知运营 ticketService.create("数据不一致", item.toString()); alarmService.send("对账失败", item.getBusinessKey()); } } }
4.4.4 可观测性(三位一体)
| 维度 | 工具 | 关键指标 |
|---|---|---|
| Trace | SkyWalking / Zipkin | 事务链路耗时、异常节点 |
| Metrics | Prometheus + Grafana | Outbox 积压数、MQ 消费延迟、TCC 异常率 |
| Log | ELK / Loki | 按 traceId 聚合日志,标记 [TXN] |
Grafana 告警规则示例:
outbox_pending_count > 100→ P2 告警tcc_cancel_failure_rate > 0→ P0 告警
4.5 企业级上线 Checklist(异常兜底专项)
| 检查项 | 验证方式 | 负责人 |
|---|---|---|
所有对外 API 支持 X-Request-ID |
Postman 测试 | 开发 |
| MQ 消费者实现幂等 | 模拟重复消息 | 开发 |
| Outbox 定时任务覆盖所有事件类型 | 代码审查 | 架构师 |
| 补偿平台可查看最近 7 天失败事件 | UI 验收 | 测试 |
| 对账任务每日运行且有报告 | 检查日志 | SRE |
| TCC 的 Cancel 方法有 P0 告警 | Chaos 演练 | SRE |
| 所有异常包含 traceId | 日志采样检查 | SRE |
本章核心思想 :
"没有完美的事务,只有完备的兜底"企业级系统不追求 100% 不出错,而是确保 "错可发现、错可恢复、错可追溯"。
五、企业级 Checklist
| 类别 | 检查项 | 是否完成 |
|---|---|---|
| 事务设计 | 所有 @Transactional 明确 rollbackFor |
☐ |
| 避免 this 调用、非 public 方法 | ☐ | |
| 幂等性 | API 层:X-Request-ID 去重 |
☐ |
| MQ 消费:Redis + DB 双幂等 | ☐ | |
| 异常处理 | 不吞异常,系统异常可重试 | ☐ |
| 业务异常快速失败,不重试 | ☐ | |
| 可观测性 | 全链路 TraceID 透传 | ☐ |
| 关键指标告警(积压 > 100) | ☐ | |
| 容灾 | 对账任务每日运行 | ☐ |
| 补偿平台支持人工干预 | ☐ | |
| 压测 | 模拟 2 倍峰值流量 | ☐ |
| 混沌演练(网络分区、DB 故障) | ☐ |
六、总结:企业级事务架构建议
| 场景 | 推荐方案 | 关键保障 |
|---|---|---|
| 单体高并发 | 乐观锁 + 异步解耦 | 细粒度事务、分段锁 |
| 普通微服务 | Outbox + MQ | 幂等消费、补偿平台、对账 |
| 金融强一致 | Seata TCC | Cancel 幂等、人工兜底 |
| 超长流程 | Saga 编排 | 补偿可重试、状态机持久化 |
终极原则:
- 简单优于复杂:能用最终一致就不用 TCC
- 可观测性 > 事务本身:没有监控的事务等于没有事务
- 对账是最后一道防线:再完美的事务也需要对账兜底