分布式系统常见陷阱清单
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 必须监控的指标
- 任务提交数 vs 完成数(差值持续增大说明有堆积)
- 失败率(超过 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 重试不够,还需要定时任务兜底:
定时扫描超时未完成的任务 → 重新投递或告警 → 提供管理界面手动干预