4. 数据库高可用架构:主从复制、读写分离、分库分表与分布式数据库的选型

聊到交易系统的数据库高可用,我脑子里第一个蹦出来的词就是「数据是命根子」。你想想看,系统挂了可以重启,服务崩了可以回滚,但数据要是丢了或者乱了,那可真就是灾难了。这一章,咱们就把数据库高可用的几个主流方案掰开揉碎了讲清楚。

核心观点:没有银弹。主从复制、读写分离、分库分表、分布式数据库,这四者不是替代关系,而是不同阶段的演进方案。选型的关键在于:你的业务规模到了哪个阶段?

4.1 主从复制(MySQL)—— 高可用的基石

主从复制,说白了就是让一台主库(Master)负责写,多台从库(Slave)负责同步数据。我习惯把它比作「一主多仆」的模式。

MySQL 的主从复制,核心是基于 binlog 的异步复制。流程大致是这样:

  1. 主库提交事务,写入 binlog。
  2. 从库的 I/O 线程拉取 binlog,写入 relay log。
  3. 从库的 SQL 线程回放 relay log,完成数据同步。

嗯,这里要注意一个坑:异步复制存在数据丢失的风险。主库挂了,但 binlog 还没传到从库,那这部分数据就丢了。我在项目中遇到过这种情况,当时凌晨三点被电话叫醒,主库磁盘坏了,从库差了最后几秒的数据,那叫一个头疼。

避坑指南:如果对数据一致性要求极高(比如交易流水),建议开启半同步复制(semi-sync replication)。它会等至少一个从库确认收到 binlog 后,才返回客户端成功。代价是写入性能会下降 10%-20%。

4.2 读写分离 —— 扛住读压力的利器

主从复制搭好了,接下来就是怎么用。很多团队一上来就搞分库分表,其实没必要。如果你的系统是「读多写少」的场景,读写分离往往是最简单有效的方案。

我个人的习惯是:写请求走主库,读请求走从库。但这里有个经典问题——主从延迟。你刚写完订单,刷新页面,结果从库还没同步,用户看到的是旧数据,这就尴尬了。

怎么解决?我常用的策略有几种:

  • 强制读主:对于强一致性的读(比如支付结果),直接走主库。
  • 延迟容忍:对于非关键查询(比如历史订单列表),允许短暂延迟。
  • 中间件方案:像 ShardingSphere 或 MyCat,可以配置读写分离规则,自动路由。

注意:读写分离并不能解决写瓶颈。如果你的系统写压力已经很大了,那就要考虑下一步——分库分表。

4.3 分库分表(ShardingSphere)—— 解决写瓶颈的利器

当单库单表的数据量达到千万甚至亿级时,你会发现:索引再优化,SQL 再精简,查询还是慢。这时候,分库分表就得上场了。

分库分表的核心思想就四个字:分而治之。把数据分散到多个数据库实例和多个表中,每个实例只承担一部分数据。

我推荐使用 ShardingSphere,它是个成熟的生态,支持分片、读写分离、分布式事务等。它的配置方式很灵活,可以用 YAML 或 Java API。

举个例子,假设我们要对订单表按用户 ID 取模分片:

# ShardingSphere 分片配置示例
shardingRule:
  tables:
    t_order:
      actualDataNodes: ds${0..1}.t_order_${0..1}
      tableStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: t_order_${user_id % 2}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds${user_id % 2}

这段配置的意思是:根据 user_id 的奇偶性,把数据路由到 ds0 或 ds1 数据库,同时表也按奇偶分片。这样,每个库和每个表的数据量都减半了。

个人经验:分库分表最怕的是「跨库查询」。比如你要查某个用户的所有订单,但订单分散在多个库,那就得做聚合查询,性能会大打折扣。所以,分片键的选择至关重要。我一般建议用用户 ID 或订单 ID 作为分片键,尽量让业务查询能落到单个分片上。

4.4 分布式数据库(TiDB)—— 终极方案?

分库分表虽然能解决问题,但运维成本很高。你要管理多个数据库实例,还要处理分布式事务、全局主键、跨库 JOIN 等问题。这时候,分布式数据库就出现了。

TiDB 是我比较看好的一个方案。它兼容 MySQL 协议,支持水平弹性扩展,而且对业务几乎透明。你想想看,不用改代码,就能自动分片、自动扩容,这多省心。

TiDB 的架构核心是:

  • TiDB Server:无状态的计算层,负责 SQL 解析和优化。
  • TiKV:分布式 KV 存储引擎,负责数据持久化。
  • PD:调度中心,负责元数据管理和负载均衡。

它的高可用机制很有意思:数据默认三副本,通过 Raft 协议保证一致性。任何一个节点挂了,集群自动切换,业务无感知。

我的建议:TiDB 适合数据量在 TB 级别以上、需要弹性扩展的场景。但如果你的数据量只有几百 GB,用 MySQL 主从 + 读写分离完全够用,没必要上分布式数据库,成本太高。

4.5 选型对比:一张表说清楚

为了让你更直观地理解,我整理了一张对比表:

方案 适用场景 优点 缺点 典型延迟
主从复制 数据备份、容灾 简单、成熟 异步复制可能丢数据 毫秒级
读写分离 读多写少、查询压力大 成本低、见效快 主从延迟问题 毫秒到秒级
分库分表 单库数据量过大、写瓶颈 线性扩展、性能好 运维复杂、跨库查询难 毫秒级
分布式数据库 海量数据、弹性扩展 透明分片、高可用 成本高、学习曲线陡 毫秒到十毫秒级

4.6 一张图看懂数据库高可用架构演进

下面这张 SVG 图,是我梳理的数据库高可用架构演进路径。从单库到分布式,每一步都有它的适用场景。

数据库高可用架构演进路径 阶段1:单库 单机 MySQL 数据量 < 500GB 阶段2:主从复制 1主N从 数据量 < 1TB 阶段3:读写分离 读从库、写主库 数据量 < 5TB 阶段4:分库分表 ShardingSphere 数据量 < 50TB 阶段5:分布式数据库 TiDB / OceanBase 数据量 > 50TB 注:每个阶段都是前一个阶段的演进,不是替代关系 选型原则:用最合适的方案,而不是最复杂的方案

4.7 我的选型建议

说了这么多,到底怎么选?我总结了几条原则:

  • 数据量 < 500GB:单库 + 主从复制就够了,别折腾。
  • 读压力大,写压力小:主从复制 + 读写分离,成本最低。
  • 单表数据量 > 1000万行:考虑分库分表,用 ShardingSphere 做透明分片。
  • 数据量 > 50TB,且需要弹性扩展:上 TiDB 或 OceanBase,省心。

最后提醒一句:不要为了技术而技术。我见过不少团队,数据量才几百 GB 就上了分库分表,结果运维成本翻了好几倍,性能反而没提升。选型之前,先问问自己:当前的问题是什么?

专注资料整理