Skip to content

数据库中间件与高可用 ​

Java 架构师 · 高并发与架构

当单库性能(连接数、CPU)与容量(单表亿级)触顶,数据库中间件通过分库分表、读写分离把压力分散;高可用则靠主从复制、故障切换保障"数据不丢、服务不断"。这是高并发架构的底层支柱——但分库分表是"不得已而为之"的重型操作,拆分后运维成本陡增。本章深入分片路由原理、分片键选型、热点扩容、双写迁移与一致性校验,以及主从延迟的工程权衡。

一、为什么要中间件:先穷尽优化再拆分 ​

动手分库分表前,必须确认已穷尽更轻的手段:

  1. SQL 优化 + 索引:最便宜的收益(避免全表扫描、深分页、N+1)。
  2. 读写分离:一主多从,分摊读压力。
  3. 升级硬件 / 换存储引擎:SSD、更大内存。
  4. 冷热分离:历史数据归档。

这些都到极限了,才上分库分表。架构师要在"性能收益"与"运维复杂度"间精算——拆分后,跨片 JOIN、分布式事务、扩容迁移都是长期成本。

二、读写分离与主从复制 ​

一主多从,写走主库、读走从库。复制基于 binlog:

  • 异步复制:主库提交即返回,从库后台追 binlog。性能最好,但有延迟(RPO>0,故障切换可能丢未同步数据)。
  • 半同步复制:主库等至少一个从库 ACK 再返回。降低延迟但牺牲写性能(见 high-concurrency 章节主从延迟治理)。

故障切换:MHA / Orchestrator / 云 RDS 自动选新主。切换要点:原主降级为从并重连,避免"双主写"导致数据冲突。

三、分库分表:水平 vs 垂直 ​

  • 垂直拆分:按业务/字段拆(用户库、订单库;大字段拆出)。隔离热点,但不解决单表行数瓶颈。
  • 水平拆分(分片):按分片键把一张大表拆到多个库/表。这是破解单表亿级瓶颈的根本手段。

ShardingSphere 分片配置示例:

yaml
rules:
  - !SHARDING
    tables:
      t_order:
        actualDataNodes: ds${0..1}.t_order_${0..15}     # 2库 × 16表
        databaseStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: db-inline            # user_id % 2
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: tbl-inline           # order_id % 16

分片路由原理:SQL 进来 → 解析 → 提取分片键 → 计算目标库/表 → 改写 SQL → 路由执行 → 结果归并(Merge)。若 SQL 没带分片键,只能广播到所有分片再归并(全路由,性能极差)。

四、分片键选型:决定成败 ​

分片键优点风险
用户 ID用户维度查询直路由(单分片)跨用户统计需广播
订单 ID订单维度均匀分散按用户查需跨片(需冗余用户维度索引表)
时间冷热分离、易归档写入倾斜到最新片(热点)

选型原则:按最高频查询维度选分片键,并预判数据倾斜。基因法:把 user_id 的低位 bits 编入 order_id(如 order_id = 时间戳 + user_id 后 N 位),这样"按 order_id 查"也能反推 user_id 路由到正确分片,避免跨片。

五、热点扩容:从取模到一致性哈希 ​

简单取模(hash(key) % N)的问题是:扩容(N 变 N+1)时几乎所有数据要重新分布 → 大规模迁移。解法:

  • 一致性哈希(Consistent Hashing):环上虚拟节点,扩容只迁移相邻数据,影响局部。适合 KV 类。
  • 范围分片:按时间/ID 范围,新范围直接新库,无需迁移旧数据(但易写入倾斜)。
  • 双写迁移(见下):无论哪种路由,扩容都靠"双写过渡"平滑进行。

六、数据迁移与双写:可回滚的平滑上线 ​

从单库迁到分库分表,绝不可"停服导数据"。标准双写迁移:

要点:

  • 双写过渡期:新旧库同时写,读逐步切到新库。
  • 全量(DataX/导数)+ 增量(Canal 追 binlog) 追平存量与增量。
  • 一致性校验:定时比对新旧库(按主键抽样/全量),不一致告警修复。
  • 可回滚:读切新库后若出问题,立刻切回旧库(旧库一直在双写,数据最新)。

分库分表的硬约束

  • 跨片查询/JOIN 不可用:分片后跨库 JOIN 无法执行,需冗余字段或业务层聚合;避免用非分片键查询(全路由)。
  • 分布式事务难:跨库事务无法单机 ACID,优先最终一致(见分布式事务章节)。
  • 热点分片:分片键选错导致数据倾斜,单分片成瓶颈(如按时间分片,最新片被打爆)。
  • 全局唯一 ID:自增失效,需分布式 ID(雪花/号段,见分布式理论章节)。
  • 分页/排序/聚合:跨片 order by/count 需各分片执行后归并,成本高。

七、高可用:主从 + 故障切换 + 多副本 ​

  • 主从复制(binlog):数据冗余,从库可顶上。
  • 故障切换:原主宕机 → 选延迟最小从库升主(Orchestrator 基于拓扑感知选主,避免"无效主")。
  • 多副本 + 仲裁:避免"两节点脑裂"——常用奇数节点(3/5)做选主仲裁(Raft 多数派思想)。
  • 备份与 PITR:定期全量 + binlog,支持按时间点恢复(RPO 兜底)。

八、中间件选型 ​

中间件形态特点
ShardingSphereJDBC 代理 / Proxy生态全、可透明接入、Proxy 模式无侵入
MyCatProxy老牌、稳定、社区弱
云 DRDS / PolarDB云服务托管、免运维、贵

经验:Java 应用优先 ShardingSphere-JDBC(无独立节点、性能好);需跨语言/无侵入用 Proxy 模式。

九、最佳实践 ​

  • 分片键按最高频查询维度选,用基因法兼顾多维度;预判倾斜。
  • 用中间件做逻辑分片,业务代码尽量无感(ShardingSphere 拦截 SQL 改写)。
  • 迁移走"双写 + 全量增量同步 + 校验 + 灰度切读 + 可回滚"。
  • 强一致读(金融)显式路由主库;监控主从延迟,超阈值告警/切读。

本节小结

数据库中间件是突破单库瓶颈的利器:分库分表解决容量、读写分离解决读压力、主从复制+故障切换解决高可用。但拆分是重操作——应先穷尽索引/SQL/读写分离/硬件优化再拆。分片键选择决定成败(按高频查询维度 + 基因法防跨片),扩容用一致性哈希/双写平滑。迁移务必"双写+全量增量同步+一致性校验+灰度切读+可回滚",绝不停服导数据。分片后跨片 JOIN、分布式事务、热点倾斜成为长期约束。架构师要在"性能收益"与"运维复杂度"间精算,把分库分表当作最后一招而非第一招。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 设计题:订单表 20 亿行,设计分库分表方案(分片键、库表数量、路由算法),说明如何用"基因法"让"按用户查"和"按订单查"都能单分片路由,并避免热点。
  2. 设计题:给出"从单库平滑迁移到分库分表"的双写上线步骤(含全量/增量/校验/灰度/回滚),并写出每步的验收条件与回滚触发条件。
  3. 设计题:分片后"按用户统计近 30 天订单"这类跨片聚合查询如何做?列出可行方案(冗余汇总表/ES/流式计算)并对比优劣。
  4. 辨析题:为什么扩容时"取模分片"要大规模迁移数据?一致性哈希如何减少迁移范围?它又引入了什么新问题(虚拟节点/数据分布)?