演进全景

让我们回顾小明的二手书平台从诞生到成熟的完整演进路径:

第一阶段:单机时代
    └── 问题:并发修改导致数据错乱
    └── 方案:数据库事务、隔离级别、乐观锁/悲观锁

第二阶段:读写分离
    └── 问题:从库延迟导致读取过期数据
    └── 方案:强制读主、时间窗口、会话级路由、同步复制

第三阶段:引入缓存
    └── 问题:数据库与缓存数据不一致
    └── 方案: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/NOTIFYPG 通知机制最终一致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 或消息驱动

常见误区

误区一:追求强一致性

很多时候,最终一致性就够了。强一致性的代价是:

  • 性能下降
  • 可用性降低
  • 实现复杂

建议:先问业务能否容忍短暂不一致,再选方案。

误区二:过早分布式

分布式是为了解决规模问题,不是为了炫技。

错误路径:
单机 → 微服务 → 分库分表 → 问题缠身

正确路径:
单机 → 优化单机 → 读写分离 → 必要时才拆服务 → 必要时才分库

建议:能单机解决的,不要分布式。

误区三:忽视幂等性

分布式系统中,任何操作都可能被执行多次:

  • 网络重试
  • 消息重复
  • 用户重复点击

建议:设计 API 时就考虑幂等,而不是事后补救。

误区四:缺少兜底机制

再完美的系统也会出问题,需要考虑:

  • 补偿失败怎么办?
  • 消息消费失败怎么办?
  • 分布式事务超时怎么办?

建议

  1. 记录完整的操作日志
  2. 设计死信队列和告警
  3. 提供人工介入入口

实战检查清单

上线前检查

单机并发

  • 关键操作有事务保护
  • 并发修改有锁机制
  • 隔离级别设置合理

读写分离

  • 关键读操作走主库
  • 有延迟监控告警
  • 有降级方案(主库承载所有)

缓存

  • 缓存有过期时间
  • 更新操作删除缓存
  • 缓存失效有重试

跨服务事务

  • 每个操作都有补偿
  • 补偿操作幂等
  • 有事务状态记录

消息

  • 生产端可靠发送
  • 消费端幂等处理
  • 有死信队列
  • 有消费监控

分片

  • 分片键选择合理
  • 跨分片查询有方案
  • 扩容方案已规划

故障排查检查清单

数据不一致时

  1. 检查事务边界是否正确
  2. 检查缓存是否已失效
  3. 检查消息是否已消费
  4. 检查补偿是否执行成功
  5. 检查是否有并发竞争

性能下降时

  1. 检查锁等待时间
  2. 检查跨分片查询数量
  3. 检查消息积压情况
  4. 检查同步复制延迟

技术栈推荐

Rust 生态

需求推荐库
PostgreSQLsqlx
Redisredis-rs
Kafkardkafka
序列化serde + serde_json
异步运行时tokio
HTTP 客户端reqwest
日志tracing

基础设施

需求推荐方案
数据库PostgreSQL
缓存Redis
消息队列Kafka / RabbitMQ
CDCDebezium
监控Prometheus + Grafana
链路追踪Jaeger

写在最后

分布式一致性没有完美方案,只有适合的方案。

小明从一个简单的二手书平台开始,随着业务增长,一步步解决了各种一致性问题:

  1. 单机时代:学会了用事务和锁
  2. 读写分离:学会了处理主从延迟
  3. 引入缓存:学会了多存储同步
  4. 数据分片:学会了跨分片一致性
  5. 服务拆分:学会了分布式事务
  6. 消息驱动:学会了可靠消息

这个过程中,小明总结出几条原则:

  1. 简单优先:能简单解决的,不要复杂化
  2. 权衡取舍:没有银弹,只有权衡
  3. 做好兜底:考虑失败场景和人工介入
  4. 持续演进:架构随业务演进,不要过度设计

希望这个系列对你有所帮助。当你遇到一致性问题时,可以回来翻翻这份指南,找到适合你场景的方案。

记住:最好的架构不是最复杂的,而是刚好能解决问题的。


上一篇:消息驱动:最终一致性

本系列:

  1. 单机时代:ACID 的庇护
  2. 读写分离:副本一致性
  3. 引入缓存:多存储一致性
  4. 数据分片:跨分片事务一致性
  5. 服务拆分:跨服务事务一致性
  6. 消息驱动:最终一致性
  7. 总结:演进全景与选型指南(本篇)

感谢阅读!