← 返回因果链

🔗 分布式系统因果链

因为…所以… — 从CAP到分布式事务的完整因果推导

CAPBASE分布式事务 分布式锁分布式ID一致性

📊 因果链总览图

❓ 单机瓶颈 💡 分布式 ⚡ 网络不可靠
CAP不可能三角
⚡ 数据一致性问题 💡 2PC→3PC→TCC
→Saga→最终一致性
⚡ 互斥访问 💡 分布式锁
Redis/ZooKeeper
⚡ 全局唯一ID 💡 雪花算法/号段模式
类比:投票选举
单机 = 一个人说了算 → 分布式 = 多人投票 → CAP = 投票时有人联系不上怎么办?
一致性 = 必须所有人都同意才行 → 可用性 = 多数人同意就行
分布式事务 = 多人同时做决定,要么都同意要么都不同意
分布式锁 = 同一时间只能一个人发言
分布式ID = 每个人有唯一编号

① 因为:单机瓶颈 → 所以分布式 → 所以CAP

❓ 问题:单机性能/容量/可用性都有限
💡 所以:分布式 → 但引入了网络不可靠问题

CAP定理:因为网络分区(P)不可避免,所以一致性和可用性只能二选一:

C 一致性 A 可用性
P 分区容忍 (必须保证)
CP:ZooKeeper/etcd vs AP:Eureka/Cassandra

BASE理论:因为CAP只能二选一,大多数业务选AP → 所以接受基本可用+软状态+最终一致性

② 因为:分布式数据不一致 → 所以分布式事务

❓ 问题:跨库/跨服务操作,要么全成功要么全失败
💡 所以:分布式事务方案演进
2PC ⚡ 同步阻塞 3PC ⚡ 还是阻塞
TCC
(Try-Confirm-Cancel)
⚡ 代码侵入大 Saga
(编排/补偿)
Seata AT模式
(无侵入+最终一致性)
// Seata AT 模式(最常用) // 因为:2PC同步阻塞 → 所以AT模式用本地事务+全局锁 // 因为:TCC代码侵入大 → 所以AT模式对业务无侵入 @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private StockClient stockClient; @GlobalTransactional(name = "create-order") // Seata全局事务 public void createOrder(OrderDTO dto) { // 一阶段:本地事务直接提交(不阻塞) orderMapper.insert(dto); // 本地事务 stockClient.deduct(dto); // 远程调用(也是本地事务) // 二阶段: // 全部成功 → 异步删除回滚日志 // 任何一个失败 → 根据回滚日志自动补偿 } } // Seata AT 原理: // 1. 一阶段:业务SQL + 生成回滚日志(undo_log表)→ 本地事务提交 // 2. 二阶段提交:异步删除undo_log // 3. 二阶段回滚:根据undo_log反向补偿(生成反向SQL)
方案一致性性能侵入性因为…所以选它
2PC强一致低(阻塞)因为要强一致 → 但性能差,少用
TCC强一致大(3个方法)因为要强一致+高性能 → 但要写Try/Confirm/Cancel
Saga最终一致中(补偿)因为长事务 → 所以编排+补偿
Seata AT最终一致无(自动)因为不想改代码 → 所以AT自动生成回滚日志
本地消息表最终一致因为简单可靠 → 所以最常用

③ 因为:分布式环境互斥 → 所以分布式锁

❓ 问题:多台机器上的线程同时修改同一资源

synchronized只能锁同一JVM内的线程,A服务和B服务在不同JVM → 锁不住!

💡 所以:分布式锁——用一个外部组件做锁
// Redis 分布式锁 // 因为:需要跨JVM互斥 → 所以用Redis的SET NX EX // ❌ 简陋版:有bug Boolean locked = redisTemplate.opsForValue() .setIfAbsent("lock:order:" + orderId, "1", 30, TimeUnit.SECONDS); // 问题:锁过期了但业务还没完成 → 其他线程拿到锁 → 并发! // 问题:线程A的锁过期了,线程B拿到了锁,线程A执行完del → 删了B的锁! // ✅ Redisson:自动续期 + 唯一标识 @Autowired private RedissonClient redisson; public void createOrder(OrderDTO dto) { RLock lock = redisson.getLock("lock:order:" + dto.getProductId()); try { if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { // 拿到锁:自动续期(watchdog),默认30s续一次 // 业务逻辑... } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); // 只删自己的锁(用唯一ID标识) } } }

Redis vs ZooKeeper 分布式锁

RedisZooKeeper
性能高(内存)较低(磁盘)
可靠性主从切换可能丢锁ZAB协议保证一致
续期Watchdog自动续临时节点+Session
因为…所以选因为性能优先 → Redis因为可靠性优先 → ZK

④ 因为:分库后自增ID冲突 → 所以分布式ID

❓ 问题:分库后各库自增ID会重复

DB1的orders表id=1,2,3... DB2的orders表也是id=1,2,3... → 合并查询时ID冲突!

💡 所以:分布式ID方案
// 方案1:UUID — 简单但无序 // 因为:UUID完全随机 → 所以B+树索引大量随机写 → 性能差 // 方案2:雪花算法 — 趋势递增 + 高性能 // 因为:需要全局唯一+趋势递增 → 所以雪花算法 // 结构:1位符号 + 41位时间戳 + 10位机器ID + 12位序列号 // 每毫秒可生成 4096 个ID // 类比:编号含日期+分店号+序号 // 20260613-门店05-0001 // ↓ 雪花ID(64位二进制) // | 0 | 0000...0000 | 0000000010 | 000000000001 | // |符号| 41位时间戳 | 10位机器ID | 12位序列号 | // 使用 Hutool 雪花算法 Snowflake snowflake = IdUtil.getSnowflake(1); // workerId=1 long id = snowflake.nextId(); // 1879348273647123456 // 方案3:号段模式(Leaf) // 因为:雪花算法依赖时钟 → 时钟回拨会重复 // 所以号段模式:DB预分配号段,服务本地消费 // 每次取1000个号:1-1000, 1001-2000, 2001-3000

🎯 面试点映射

面试题因为…所以…
CAP定理因为网络分区不可避免 → 所以C和A只能二选一
BASE理论因为大多数业务选AP → 所以接受基本可用+最终一致性
分布式事务方案因为跨库事务本地管不了 → 所以2PC/TCC/Saga/Seata
2PC vs TCC vs Saga因为2PC阻塞 → 所以TCC非阻塞但代码侵入 → Saga长事务编排
分布式锁因为synchronized跨JVM无效 → 所以用Redis NX/ZK临时节点
Redisson看门狗因为锁过期但业务没完 → 所以Watchdog自动续期
雪花算法因为分库自增ID重复+UUID无序 → 所以雪花(时间戳+机器ID+序列号)
时钟回拨因为雪花依赖时间戳 → 所以时钟回拨会生成重复ID → 号段模式兜底

📌 因果链总结

单机
瓶颈
分布式 CAP
不可能三角
BASE
最终一致
跨库
事务
2PC/TCC
/Saga/AT
互斥
访问
分布式
唯一
ID
雪花
算法
全栈资料库 · 因果链板块 · 分布式系统因果链