4. 数据库高可用架构:主从复制、读写分离、分库分表与分布式数据库的选型
聊到交易系统的数据库高可用,我脑子里第一个蹦出来的词就是「数据是命根子」。你想想看,系统挂了可以重启,服务崩了可以回滚,但数据要是丢了或者乱了,那可真就是灾难了。这一章,咱们就把数据库高可用的几个主流方案掰开揉碎了讲清楚。
核心观点:没有银弹。主从复制、读写分离、分库分表、分布式数据库,这四者不是替代关系,而是不同阶段的演进方案。选型的关键在于:你的业务规模到了哪个阶段?
4.1 主从复制(MySQL)—— 高可用的基石
主从复制,说白了就是让一台主库(Master)负责写,多台从库(Slave)负责同步数据。我习惯把它比作「一主多仆」的模式。
MySQL 的主从复制,核心是基于 binlog 的异步复制。流程大致是这样:
- 主库提交事务,写入 binlog。
- 从库的 I/O 线程拉取 binlog,写入 relay log。
- 从库的 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 图,是我梳理的数据库高可用架构演进路径。从单库到分布式,每一步都有它的适用场景。
4.7 我的选型建议
说了这么多,到底怎么选?我总结了几条原则:
- 数据量 < 500GB:单库 + 主从复制就够了,别折腾。
- 读压力大,写压力小:主从复制 + 读写分离,成本最低。
- 单表数据量 > 1000万行:考虑分库分表,用 ShardingSphere 做透明分片。
- 数据量 > 50TB,且需要弹性扩展:上 TiDB 或 OceanBase,省心。
最后提醒一句:不要为了技术而技术。我见过不少团队,数据量才几百 GB 就上了分库分表,结果运维成本翻了好几倍,性能反而没提升。选型之前,先问问自己:当前的问题是什么?