一、从一次"扣钱成功但没发货"说起
2018年双十一,我经历了职业生涯中最难忘的一次故障。
当时的订单下单流程是:
- 订单服务:创建订单,状态=待支付
- 支付服务:扣减用户余额
- 库存服务:扣减商品库存
- 订单服务:更新订单状态=已支付,发货
有一天,监控系统报警:有个用户的账户扣了500块,但订单显示"待支付",商品也没发货!
钱没了,东西没到手。用户打电话投诉,客服吓坏了。
我们排查了整整3个小时,发现问题出在:
T1: 订单服务创建订单成功
T2: 支付服务扣款成功
T3: 库存服务扣库存成功
T4: 订单服务更新状态时------网络抖动,更新失败!
结果:钱扣了,货没发,订单状态还是"待支付"。
这是典型的分布式事务问题:一个操作涉及多个服务,要么全部成功,要么全部失败。但在分布式系统里,保证这一点非常困难。
二、什么是分布式事务
2.1 从本地事务说起
传统关系型数据库的事务(ACID):
sql
BEGIN TRANSACTION;
-- 扣钱
UPDATE account SET balance = balance - 500 WHERE user_id = 1;
-- 减库存
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
-- 创建订单
INSERT INTO orders (user_id, product_id, amount) VALUES (1, 100, 500);
COMMIT;
本地事务的保障:
- Atomic(原子性):要么全部执行,要么全部回滚
- Consistent(一致性):事务前后数据总量不变
- Isolated(隔离性):并发事务互不干扰
- Durable(持久性):提交后数据持久保存
2.2 分布式事务的问题
sql
-- 分布式场景下,这3个操作可能在3个不同的数据库!
BEGIN TRANSACTION; -- 谁来管理这个跨数据库的事务?
-- 订单库
INSERT INTO orders ...; -- 在数据库A
-- 支付库
UPDATE account ...; -- 在数据库B
-- 库存库
UPDATE inventory ...; -- 在数据库C
COMMIT; -- 谁来提交/回滚?
没有分布式事务管理时:
- T1成功,T2成功,T3失败 → 数据不一致
- T1成功,T2失败 → 订单有了,钱没扣
- T1失败,T2成功 → 钱扣了,没订单
2.3 CAP定理与BASE理论
CAP定理:
一个分布式系统不能同时满足 Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)。
分布式系统必须保证P(分区容错性)
所以只能在C(一致性)和A(可用性)之间选择
CP系统:优先保证一致性(如ZooKeeper、HBase)
AP系统:优先保证可用性(如Cassandra、Eureka)
BASE理论:
- Basically Available:基本可用,允许部分失败
- Soft state:软状态,数据可以暂时不一致
- Eventually consistent:最终一致性,系统最终会达到一致状态
2.4 分布式事务的解决方案
| 方案 | 特点 | 适用场景 |
|---|---|---|
| 2PC/3PC | 强一致,性能差 | 对数据一致性要求极高 |
| TCC | 最终一致,性能较好 | 跨服务、跨库 |
| Saga | 最终一致,性能好 | 长事务 |
| 消息队列 | 最终一致,异步处理 | 可接受延迟 |
| 本地消息表 | 最终一致,可靠消息 | 准实时场景 |
三、Seata概述:分布式事务解决方案
3.1 Seata是什么
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,提供了AT、TCC、Saga、XA四种事务模式。
Seata架构:
┌─────────────────────────────────────────────────────────────┐
│ Seata Server │
│ (事务协调器 TC) │
│ TC = Transaction Coordinator │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Global │ │ Branch │ │ Session │ │
│ │ Transaction│ │ Transaction │ │ Manager │ │
│ │ Handler │ │ Handler │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└────────────────────────────┬────────────────────────────────┘
│ 协调通信
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 应用服务A │ │ 应用服务B │ │ 应用服务C │
│ │ │ │ │ │
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
│ │ TM │ │ │ │ TM │ │ │ │ TM │ │
│ │(事务管理器)│ │ │ │(事务管理器)│ │ │ │(事务管理器)│ │
│ └────┬───┘ │ │ └────┬───┘ │ │ └────┬───┘ │
│ │ │ │ │ │ │ │ │
│ ┌────▼───┐ │ │ ┌────▼───┐ │ │ ┌────▼───┐ │
│ │ RM │ │ │ │ RM │ │ │ │ RM │ │
│ │(资源管理器)│ │ │ │(资源管理器)│ │ │ │(资源管理器)│ │
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
└──────────────┘ └──────────────┘ └──────────────┘
角色说明:
- TC (Transaction Coordinator): Seata服务端,协调全局事务
- TM (Transaction Manager): 事务管理器,发起/提交/回滚全局事务
- RM (Resource Manager): 资源管理器,管理分支事务的资源
3.2 Seata四种模式
Seata事务模式:
1. AT模式(Auto Transaction)
- 自动处理,高层API
- 基于本地事务 + Undolog
- 无需改业务代码
- 适合:同库/跨库操作
2. TCC模式(Try-Confirm-Cancel)
- 两阶段提交
- 业务代码实现
- 性能最好
- 适合:跨服务调用
3. Saga模式
- 长事务编排
- 补偿机制
- 可异步执行
- 适合:长流程
4. XA模式
- 数据库原生支持
- 强一致性
- 性能较低
- 适合:对数据一致性要求极高
四、Seata AT模式实战
4.1 AT模式原理
AT模式的核心思想:利用本地事务的ACID特性,通过Undolog记录数据变化,实现自动补偿。
AT模式执行流程:
全局事务开启
↓
┌─────────────────────────────────────────────────┐
│ 第一阶段(自动,无感知) │
│ │
│ Branch Transaction 1: │
│ BEGIN; │
│ UPDATE account SET balance = balance - 500; │
│ INSERT INTO undo_log ...; ← 记录修改前的镜像 │
│ COMMIT; │
│ │
│ Branch Transaction 2: │
│ BEGIN; │
│ UPDATE inventory SET stock = stock - 1; │
│ INSERT INTO undo_log ...; ← 记录修改前的镜像 │
│ COMMIT; │
│ │
│ 全局事务提交/回滚? │
│ ↓ │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 第二阶段(根据结果自动处理) │
│ │
│ 全局事务提交: │
│ DELETE FROM undo_log; ← 删除undolog │
│ (所有分支事务都已提交,数据已生效) │
│ │
│ 全局事务回滚: │
│ 根据undo_log生成反向SQL │
│ UPDATE account SET balance = balance + 500; │
│ UPDATE inventory SET stock = stock + 1; │
│ DELETE FROM undo_log; ← 删除undolog │
│ │
└─────────────────────────────────────────────────┘
4.2 Seata Server部署
yaml
# docker-compose.yml
version: '3'
services:
seata:
image: seataio/seata-server:2.0.0
container_name: seata-server
environment:
- SEATA_CONFIG_NAME=file
- STORE_MODE=db
- SERVER_NODE=1
ports:
- "8091:8091"
volumes:
- ./registry.conf:/root/seata-config/registry.conf
- ./file.conf:/root/seata-config/file.conf
depends_on:
- mysql
networks:
- seata-net
mysql:
image: mysql:8.0
container_name: seata-mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: seata
ports:
- "3306:3306"
networks:
- seata-net
command: --default-authentication-plugin=mysql_native_password
networks:
seata-net:
driver: bridge
properties
# file.conf
store {
mode = "db"
db {
datasource = "druid"
db-type = "mysql"
driver-class-name = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://mysql:3306/seata?useUnicode=true&characterEncoding=utf8"
username = "root"
password = "root123"
min-conn = 10
max-conn = 100
global-table = "global_table"
branch-table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
server {
recovery {
committing-retry-period = 1000
asyn-committing-retry-period = 1000
}
undo {
log-table = "undo_log"
}
}
conf
# registry.conf
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
4.3 数据库初始化
sql
-- Seata需要的表
CREATE TABLE IF NOT EXISTS `global_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`status` TINYINT NOT NULL,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `branch_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT NOT NULL,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256) NOT NULL,
`status` TINYINT NOT NULL,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`xid`,`branch_id`),
KEY `idx_xid_status` (`xid`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `lock_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(32),
`status` TINYINT NOT NULL DEFAULT '0',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`,`branch_id`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 每个业务库需要undo_log表
CREATE TABLE IF NOT EXISTS `undo_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`branch_id` XIDARG NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
KEY `ix_log_created` (`log_created`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
4.4 Spring Boot集成Seata
xml
<!-- Maven依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.2</version>
</dependency>
<!-- Seata自动配置 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group # 事务分组
enable-auto-data-source-proxy: true # 自动代理数据源
# 服务注册中心
registry:
type: nacos
nacos:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
application: seata-server
group: SEATA_GROUP
# 配置中心
config:
type: nacos
nacos:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
group: SEATA_GROUP
# Seata事务分组配置(需要和Nacos中的配置一致)
service:
vgroup-mapping:
my_test_tx_group: default # 事务分组 -> 集群名
4.5 代码中使用Seata
java
// 方式1:全局事务(@GlobalTransactional)
// 在Controller或Service入口方法添加注解
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountFeignClient accountFeignClient;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Override
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(Long userId, Long productId, Integer count) {
// 1. 创建订单(本地事务,Seata自动管理)
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setStatus("PENDING");
order.setCreateTime(new Date());
orderMapper.insert(order);
// 2. 扣减账户余额(远程调用,Seata自动开启分支事务)
accountFeignClient.deduct(userId, order.getAmount());
// 3. 扣减库存(远程调用,Seata自动开启分支事务)
inventoryFeignClient.deduct(productId, count);
// 4. 更新订单状态
order.setStatus("PAID");
orderMapper.updateById(order);
return order;
}
}
五、Seata TCC模式实战
5.1 TCC原理
TCC模式需要业务代码实现三个方法:
-
Try:预留资源,冻结数据
-
Confirm:确认执行,使用预留资源
-
Cancel:取消执行,释放预留资源
TCC执行流程:
全局事务开启
↓
┌─────────────────────────────────────────────────┐
│ Try阶段(所有服务都要成功) │
│ │
│ AccountService.try(): │
│ 冻结用户500元(balance_frozen += 500) │
│ │
│ InventoryService.try(): │
│ 冻结库存1件(stock_frozen += 1) │
│ │
│ 结果:资源已预留,要么确认,要么回滚 │
└─────────────────────────────────────────────────┘
↓
├─ 所有Try成功 → Confirm阶段
└─ 任一Try失败 → Cancel阶段┌─────────────────────────────────────────────────┐
│ Confirm阶段(所有服务确认) │
│ │
│ AccountService.confirm(): │
│ balance -= 500(扣真实余额) │
│ balance_frozen -= 500(解冻) │
│ │
│ InventoryService.confirm(): │
│ stock -= 1(扣真实库存) │
│ stock_frozen -= 1(解冻) │
│ │
└─────────────────────────────────────────────────┘┌─────────────────────────────────────────────────┐
│ Cancel阶段(所有服务回滚) │
│ │
│ AccountService.cancel(): │
│ balance_frozen -= 500(解冻就相当于回滚) │
│ │
│ InventoryService.cancel(): │
│ stock_frozen -= 1(解冻就相当于回滚) │
│ │
└─────────────────────────────────────────────────┘
5.2 TCC实现代码
java
// TCC接口定义
@LocalTCC
public interface AccountTccService {
@TwoPhaseBusinessAction(
name = "deductAccount",
commitMethod = "confirm",
rollbackMethod = "cancel"
)
// Try方法:预留资源
boolean try deduct(
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount
);
// Confirm方法:确认执行
boolean confirm(BusinessActionContext context);
// Cancel方法:回滚
boolean cancel(BusinessActionContext context);
}
java
// TCC接口实现
@Service
public class AccountTccServiceImpl implements AccountTccService {
@Autowired
private AccountMapper accountMapper;
@Override
@Transactional
public boolean try deduct(Long userId, BigDecimal amount) {
// 1. 查询账户
Account account = accountMapper.selectByUserId(userId);
if (account == null) {
throw new RuntimeException("账户不存在");
}
// 2. 检查余额是否足够
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
// 3. 冻结金额 = 余额 - 冻结部分 = 可用金额
// 实际实现中,可以在freeze_balance字段记录
accountMapper.freezeBalance(userId, amount);
return true;
}
@Override
public boolean confirm(BusinessActionContext context) {
// 获取Try阶段保存的数据
Long userId = Long.valueOf(context.getActionContext("userId").toString());
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
// 真正扣减余额(余额已在Try阶段冻结,这里直接扣除)
accountMapper.deductBalance(userId, amount);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
// 获取Try阶段保存的数据
Long userId = Long.valueOf(context.getActionContext("userId").toString());
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
// 释放冻结的金额
accountMapper.unfreezeBalance(userId, amount);
return true;
}
}
5.3 TCC空回滚与悬挂问题
java
// TCC常见问题的处理
// 问题1:空回滚
// Try方法没执行,但Cancel执行了
// 原因:Try超时了,TC认为Try失败,实际可能成功了
@Override
public boolean cancel(BusinessActionContext context) {
// 检查是否已执行过Try
Long userId = Long.valueOf(context.getActionContext("userId").toString());
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
// 查询是否有冻结记录
Account account = accountMapper.selectByUserId(userId);
// 如果没有冻结记录,说明是空回滚,直接返回成功
if (account.getFreezeBalance().compareTo(amount) < 0) {
return true; // 空回滚,幂等处理
}
// 有冻结记录,正常释放
accountMapper.unfreezeBalance(userId, amount);
return true;
}
// 问题2:悬挂
// Cancel比Try先执行了
// 原因:网络问题,Try超时返回失败,但Try实际上成功了
@Override
@Transactional
public boolean try deduct(Long userId, BigDecimal amount) {
// 检查是否已经Cancel过(幂等检查)
Account account = accountMapper.selectByUserId(userId);
// 如果可用余额已经扣减了(说明被Cancel过了)
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("重复扣款");
}
// 正常冻结
accountMapper.freezeBalance(userId, amount);
return true;
}
六、Seata Saga模式实战
6.1 Saga模式原理
Saga模式适合长流程事务,每个参与者执行自己的本地事务,如果某个步骤失败,则执行前面步骤的补偿事务。
Saga执行流程:
下单流程:
Step 1: 创建订单 (正向) → Compensation: 删除订单
↓
Step 2: 扣减余额 (正向) → Compensation: 退还余额
↓
Step 3: 扣减库存 (正向) → Compensation: 恢复库存
↓
Step 4: 发送消息通知 (正向) → Compensation: 撤回消息
↓
完成
如果Step 3失败:
Step 1: 补偿:删除订单
Step 2: 补偿:退还余额
结束
6.2 Saga状态机配置
json
// saga_state_machine.json
{
"Name": "order-saga",
"Comment": "订单创建Saga流程",
"StartState": "CreateOrder",
"States": {
"CreateOrder": {
"Type": "ServiceTask",
"ServiceName": "order-service",
"ServiceMethod": "createOrder",
"CompensateState": "CancelOrder",
"Next": "DeductAccount",
"Status": {
"Success": "SU",
"BusinessError": "FA"
}
},
"CancelOrder": {
"Type": "ServiceTask",
"ServiceName": "order-service",
"ServiceMethod": "cancelOrder",
"Status": {
"Success": "SU"
}
},
"DeductAccount": {
"Type": "ServiceTask",
"ServiceName": "account-service",
"ServiceMethod": "deduct",
"CompensateState": "RefundAccount",
"Next": "DeductInventory",
"Input": [
"$.[context.userId]",
"$.[context.amount]"
],
"Status": {
"Success": "SU",
"Fail": "FA"
}
},
"RefundAccount": {
"Type": "ServiceTask",
"ServiceName": "account-service",
"ServiceMethod": "refund",
"Status": {
"Success": "SU"
}
},
"DeductInventory": {
"Type": "ServiceTask",
"ServiceName": "inventory-service",
"ServiceMethod": "deduct",
"CompensateState": "RestoreInventory",
"Next": "SuccessEnd",
"Input": [
"$.[context.productId]",
"$.[context.count]"
]
},
"RestoreInventory": {
"Type": "ServiceTask",
"ServiceName": "inventory-service",
"ServiceMethod": "restore"
},
"SuccessEnd": {
"Type": "Succeed"
}
}
}
七、生产环境最佳实践
7.1 事务分组与隔离
yaml
# 不同业务使用不同事务分组,互不影响
seata:
tx-service-group:
# 订单相关服务
order-service-group: order-tx-group
# 支付相关服务
payment-service-group: payment-tx-group
# 库存相关服务
inventory-service-group: inventory-tx-group
service:
vgroup-mapping:
order-tx-group: seata-cluster-order
payment-tx-group: seata-cluster-payment
inventory-tx-group: seata-cluster-inventory
7.2 超时配置
yaml
# 全局事务超时时间(默认60秒)
seata:
client:
rm:
report-retry-interval: 5
report-success-enable: false
tm:
deploy:
async-commit-timeout: 60000 # 异步提交超时
rollback-retry-timeout: 60000 # 回滚重试超时
7.3 性能优化
yaml
# 优化建议
# 1. Seata服务端调优
seata:
server:
session:
mode: db # 存储模式:file/redis/db
reorder-queue-capacity: 10000 # 重排序队列容量
max-branch-inquiry-limit: 100 # 最大分支查询数
# 2. 分支事务并行执行
@Transactional
@GlobalTransactional(name = "parallel-order", parallelThreadCount = 10)
public void createOrderParallel() {
// 分支事务会并行执行,提高性能
accountFeignClient.deduct(userId, amount); // 并行1
inventoryFeignClient.deduct(productId, count); // 并行2
}
# 3. 读写分离支持
seata:
client:
support:
spring:
datasource:
enable-auto-data-source-proxy: true
7.4 监控与告警
yaml
# Seata监控指标(Prometheus)
management:
metrics:
export:
prometheus:
enabled: true
# Seata控制台查看事务状态
# http://seata-server:8091
yaml
# Prometheus告警规则
groups:
- name: seata-alerts
rules:
- alert: SeataTransactionTimeout
expr: seata_transaction_timeout_total > 0
for: 1m
labels:
severity: warning
annotations:
summary: "Seata事务超时"
八、踩坑实录
坑1:Seata事务不生效
配置了@GlobalTransactional,但异常时不回滚。
原因:try-catch吞掉了异常,没有抛出到Seata。
解决:确保异常能抛出,或在catch中手动throw。
java
// 错误写法
try {
accountFeignClient.deduct(userId, amount);
} catch (Exception e) {
log.error("扣款失败", e);
// 异常被吞掉了!
}
// 正确写法
try {
accountFeignClient.deduct(userId, amount);
} catch (Exception e) {
log.error("扣款失败", e);
throw new RuntimeException("扣款失败", e); // 重新抛出
}
坑2:TCC服务间调用超时
Try阶段超时,但实际执行成功了,导致空回滚把钱退回去了。
解决:TCC模式必须处理幂等和空回滚问题。
坑3:Seata Server单点问题
Seata Server只有一台挂了,所有分布式事务都失败了。
解决:Seata Server集群部署,使用db模式存储事务状态。
坑4:全局锁竞争导致性能下降
高并发下,全局锁竞争严重,TPS下降明显。
解决:
- 使用AT模式代替TCC,减少锁范围
- 优化数据库锁表结构
- 分库分表减少热点
九、总结
分布式事务是微服务架构的难题:
- Seata AT:自动处理,低侵入,适合大多数场景
- Seata TCC:性能最好,需要业务实现补偿逻辑
- Seata Saga:适合长流程,支持异步编排
- Seata XA:强一致性,性能较低
最佳实践:
- 优先使用AT模式,业务代码零改动
- TCC模式要注意幂等、空回滚、悬挂三个问题
- Saga模式适合编排长流程业务
- 事务分组隔离,不同业务互不影响
- Seata Server集群部署保证高可用
- 全局事务超时时间要合理设置
血的教训:
分布式事务不是万能药,它会带来性能损耗和复杂性。能不用分布式事务就尽量不用,可以通过:
- 避免跨服务事务(合并服务)
- 最终一致性代替强一致性(消息队列)
- 补偿机制(定期对账)
只有真正需要强一致性时,才考虑分布式事务。
思考题: 你们系统有没有遇到过分布式事务问题?是怎么解决的?如果让你设计一个下单流程,你会用Seata的哪种模式?
个人观点,仅供参考