分布式系统常见陷阱清单

#分布式 #最佳实践

1. 分布式事务

跨服务的数据一致性是分布式系统最难搞的问题,三种方案按场景选:

方案 一致性 性能 适用场景
2PC 强一致 金融核心交易,几乎不用
TCC 最终一致 库存扣减、订单支付
事务消息 最终一致 最好 积分发放、通知类异步场景

实际项目里 2PC 用得越来越少,协调者单点 + 性能差,高并发扛不住。大多数团队直接在 TCC 和事务消息之间选。

1.1 TCC

三个阶段,业务方要实现三个接口:

Try:预留资源(冻结账户 100 元)
Confirm:真正执行(扣减冻结的 100 元)
Cancel:释放预留(解冻 100 元)

注意:Confirm 和 Cancel 必须保证成功,要做好幂等和重试。实现成本比事务消息高。

1.2 事务消息(推荐)

以 RocketMQ 为例:

1. 发送半消息(消费方看不到)
2. 执行本地事务
3. 本地事务成功 → 提交消息;失败 → 回滚消息
4. 消费方消费消息,处理业务
5. MQ 定期回查本地事务状态(兜底)

关键点:回查时间必须大于业务处理时间,否则业务还没跑完就被误判失败触发回滚。

消费方必须保证幂等,消息可能重复投递。

2. 分布式缓存

只要用缓存,就一定会有短暂的数据不一致,这是无法完全避免的,只能缩短窗口期。详细方案见 缓存一致性解决方案

几个高频问题:

缓存穿透:查询不存在的数据,每次都打到 DB。
用布隆过滤器拦截,或者缓存空值(设短过期时间)。

缓存击穿:热点 key 过期,瞬间大量请求打到 DB。
用分布式锁,只让一个请求去查 DB,其他等待。

缓存雪崩:大量 key 同时过期。
过期时间加随机偏移,避免集中失效。

3. 异步任务

异步任务本质是用时间换吞吐量,但"发出去就不管了"是最大的坑。

3.1 必须监控的指标

3.2 幂等性

消息可能重复投递,任务必须保证幂等:

// 方式1:唯一 ID 去重,执行前检查
if (taskDao.exists(taskId)) return;

// 方式2:数据库唯一约束,重复插入直接捕获
try {
    taskDao.insert(task);
} catch (DuplicateKeyException e) {
    // 已处理,忽略
}

// 方式3:状态机,只允许特定状态流转
if (order.getStatus() != OrderStatus.PENDING) return;

3.3 补偿机制

光靠 MQ 重试不够,还需要定时任务兜底:

定时扫描超时未完成的任务 → 重新投递或告警 → 提供管理界面手动干预

相关链接