从单机到分布式:一致性实战(七)总结:演进全景与选型指南
演进全景 让我们回顾小明的二手书平台从诞生到成熟的完整演进路径: 第一阶段:单机时代 └── 问题:并发修改导致数据错乱 └── 方案:数据库事务、隔离级别、乐观锁/悲观锁 第二阶段:读写分离 └── 问题:从库延迟导致读取过期数据 └── 方案:强制读主、时间窗口、会话级路由、同步复制 第三阶段:引入缓存 └── 问题:数据库与缓存数据不一致 └── 方案:Cache-Aside、延迟双删、CDC、LISTEN/NOTIFY 第四阶段:数据分片 └── 问题:跨分片查询和事务 └── 方案:并行查询、全局索引、CQRS、Saga、消息驱动 第五阶段:服务拆分 └── 问题:跨服务操作无法保证原子性 └── 方案:TCC、Saga、本地消息表、AT 模式 第六阶段:消息驱动 └── 问题:消息丢失、重复、乱序 └── 方案:本地消息表、幂等消费、顺序保证、死信队列 每一个阶段都是为了解决上一阶段的瓶颈,但同时也引入了新的一致性挑战。没有银弹,只有权衡。 一致性问题分类 按数据存储分类 类型 问题描述 典型场景 章节 单存储并发 多个请求同时修改同一数据 库存扣减、余额变更 第一章 主从复制延迟 从库数据落后于主库 读写分离架构 第二章 多存储不一致 数据库与缓存数据不同步 缓存架构 第三章 跨分片一致性 分片间数据操作不一致 分库分表架构 第四章 跨服务事务 多服务操作无法原子提交 微服务架构 第五章 消息可靠性 消息丢失/重复/乱序 事件驱动架构 第六章 按一致性强度分类 强一致性 最终一致性 │ │ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 单机事务 │ │ 同步复制 │ │ TCC │ │ Saga │ │ 消息驱动 │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ 性能最低 性能较低 性能中等 性能较高 性能最高 解决方案速查表 单机并发问题 方案 原理 优点 缺点 适用场景 数据库事务 ACID 保证 简单可靠 单机限制 单数据库场景 悲观锁 SELECT FOR UPDATE 强一致 阻塞等待 冲突频繁 乐观锁 版本号检查 无阻塞 需要重试 冲突较少 SERIALIZABLE 最高隔离级别 无并发问题 性能差 金融核心 读写分离一致性 方案 原理 延迟容忍 性能影响 适用场景 强制读主 关键操作读主库 零延迟 高 余额查询 时间窗口 写后短时间读主 秒级 中 普通业务 会话级路由 同会话读主 会话内零延迟 中 用户体验敏感 同步复制 等待从库确认 零延迟 高 强一致要求 业务容忍 接受延迟 秒级 无 非关键数据 缓存一致性 方案 原理 一致性强度 实现复杂度 适用场景 Cache-Aside 先更DB再删缓存 最终一致 低 通用场景 延迟双删 二次删除 最终一致(更强) 中 高一致要求 Write-Through 同时写DB和缓存 最终一致 中 缓存托管 Write-Behind 异步批量写DB 弱一致 高 高写入场景 CDC 监听DB变更 最终一致 高 无侵入需求 LISTEN/NOTIFY PG 通知机制 最终一致 低 PostgreSQL 跨服务事务 方案 原理 隔离性 性能 实现复杂度 适用场景 TCC 预留-确认-取消 好 中 高 短事务、金融 Saga 正向+补偿 差 高 中 长事务 本地消息表 事务写消息 差 高 低 异步场景 AT 模式 自动undo log 中 低 高 低侵入需求 消息可靠性 问题 方案 原理 消息丢失 本地消息表 事务保证写入 消息丢失 事务消息 半消息机制 消息重复 幂等消费 去重+业务幂等 消息乱序 单分区 同 key 同分区 消息乱序 序列号检查 消费端校验 消费失败 死信队列 隔离问题消息 跨分片一致性 问题 方案 一致性 性能 跨分片查询 并行查询 强一致 差 跨分片查询 全局索引 最终一致 中 跨分片查询 CQRS 最终一致 好 跨分片事务 2PC 强一致 差 跨分片事务 Saga 最终一致 好 跨分片事务 消息驱动 最终一致 好 选型决策树 总体决策流程 遇到一致性问题 │ ├─ 1. 先问:真的需要强一致吗? │ ├─ 金融、交易、核心业务 → 需要 │ └─ 统计、展示、非核心 → 最终一致即可 │ ├─ 2. 再问:能简化架构吗? │ ├─ 能不拆服务就不拆 │ ├─ 能不分库就不分 │ └─ 能单机解决就单机 │ └─ 3. 最后:选择合适的方案 └─ 见下方详细决策树 详细决策树 ┌─────────────────────────────────────────────────────────────┐ │ 一致性问题决策树 │ └─────────────────────────────────────────────────────────────┘ 问题类型是什么? │ ├─ 单库并发问题 │ ├─ 冲突频率高? → 悲观锁 (SELECT FOR UPDATE) │ ├─ 冲突频率低? → 乐观锁 (版本号) │ └─ 极端要求? → SERIALIZABLE 隔离级别 │ ├─ 读写分离延迟 │ ├─ 能容忍秒级延迟? → 业务容忍 │ ├─ 需要写后即读一致? → 时间窗口读主 │ ├─ 整个会话需要一致? → 会话级路由 │ └─ 绝对不能延迟? → 同步复制(慎用) │ ├─ 缓存不一致 │ ├─ 通用场景? → Cache-Aside │ ├─ 需要更强一致? → 延迟双删 │ ├─ 不能改代码? → CDC │ └─ 用 PostgreSQL? → LISTEN/NOTIFY │ ├─ 跨服务事务 │ ├─ 需要资源隔离? → TCC │ ├─ 是长事务? → Saga │ ├─ 可以异步? → 本地消息表 │ └─ 要低侵入? → AT 模式(如 Seata) │ ├─ 消息可靠性 │ ├─ 担心丢失? → 本地消息表 + At-Least-Once │ ├─ 担心重复? → 幂等消费 │ ├─ 担心乱序? → 单分区 + 序列号 │ └─ 担心堆积? → 死信队列 + 告警 │ └─ 跨分片问题 ├─ 查询问题 │ ├─ 查询少? → 并行查询所有分片 │ ├─ 查询复杂? → CQRS + 查询库 │ └─ 需要实时? → 全局索引 └─ 事务问题 ├─ 能避免跨分片? → 调整分片策略 ├─ 必须强一致? → 2PC(慎用) └─ 可最终一致? → Saga 或消息驱动 常见误区 误区一:追求强一致性 很多时候,最终一致性就够了。强一致性的代价是: ...