高可用与分布式架构
大约 5 分钟
高可用与分布式架构
单机数据库的三大瓶颈:可用性(宕机服务中断)、读性能、写容量与存储。本篇讲对应的解法。
主从复制
一主多从:主库写,从库读(读写分离),同时作为灾备。
复制原理(三线程模型)
主库 从库
业务写 → binlog → IO 线程拉取 → relay log(中继日志)
→ SQL 线程重放 → 从库数据
- 主库提交事务时写 binlog
- 从库 IO 线程连接主库,主库 dump 线程推送 binlog 事件
- 从库写入 relay log,SQL 线程(或 8.0 的并行复制线程)重放
三种复制格式
| 格式 | 原理 | 特点 |
|---|---|---|
| STATEMENT | 记 SQL 语句 | 日志小;NOW()、UUID() 等不确定性函数会不一致 |
| ROW(默认) | 记每行变更 | 一致性最好;日志较大 |
| MIXED | 自动切换 | 折中 |
主从延迟
原因:从库单线程重放追不上主库并发写;大事务;从库机器差、负载高。
影响:写完立刻读从库,可能读不到(「我刚发布的动态呢?」)。
应对:
1. 业务层:写后强制读主库(关键路径)
2. 半同步复制:至少一个从库收到 binlog 才返回提交(损失少许写吞吐换不丢)
3. 开启并行复制(GTID + LOGICAL_CLOCK)
4. 避免大事务、避免从库承载重查询
5. MGR / 分布式方案(见下)
GTID
全局事务标识符(server_uuid:transaction_id)。相比传统「文件名 + 位点」复制:
- 从库自动定位断点,故障切换时不用手工指位点
- 搭配
mysqldump --master-data=2、CHANGE MASTER TO MASTER_AUTO_POSITION=1
高可用方案演进
| 方案 | 原理 | 切换方式 |
|---|---|---|
| MHA / Orchestrator | 监控主库,宕机后提升某从库为主 | 外部工具秒级切换,需处理 VIP/域名 |
| MGR(Group Replication) | 基于 Paxos 的多节点强一致 | 自动选主,8.0 推荐 |
| 分布式数据库 | TiDB 等,Raft 多副本 + 自动调度 | 原生高可用 |
MGR 要点
- 多数派(N/2+1)存活即可服务,建议 3 或 5 节点
- 单主模式(类似主从)或多主模式(冲突检测,慎用)
- 仅支持 InnoDB、需要 GTID、每个表要有主键
读写分离
中间件或代理层实现:
- ProxySQL / MySQL Router:数据库代理,应用无感知
- 应用层:ORM 配置多数据源(如 GORM 的
DBResolver、ShardingSphere-JDBC) - 逻辑:写操作路由主库,读操作路由从库;需要强一致的读强制走主库
分库分表
单表经验阈值:1000 万行 / 20GB / 单机磁盘 IO 打满,先优化索引和 SQL,再考虑拆。
垂直拆分 vs 水平拆分
垂直分库:按业务域拆 —— 订单库、用户库、商品库(服务化/微服务配套)
垂直分表:宽表按列拆 —— 高频小列 + 低频大列(详情拆出去)
水平分表:同结构表横向切多份 —— order_0 ~ order_99,按分片键路由
分片键的选择(最关键决策)
查询最高频的维度 = 分片键。
电商订单:user_id(买家查单最频繁)
→ 同一用户的订单落同一张表,单表查询不跨片
→ 商家侧查询需要异构一张「按商家分片」的表(数据双写/订阅 binlog 同步)
分片算法
| 算法 | 优点 | 缺点 |
|---|---|---|
| 哈希取模 | 均匀 | 扩容要迁移数据 |
| range 按范围 | 扩容友好 | 热点(新数据都在最后一片) |
| 一致性哈希 | 扩容只迁移相邻数据 | 实现复杂 |
| 查表法(基因法) | 灵活 | 多一层映射维护 |
分库分表带来的问题
- 分布式 ID:自增 ID 冲突 → 雪花算法、号段模式、
UUIDv7 - 跨片查询:非分片键查询要扫全部分片(ES 异构/宽表冗余)
- 跨片 JOIN:业务层组装或全局表(每个库冗余一份字典表)
- 分布式事务:见下节
常用中间件
- ShardingSphere(Java 生态,JDBC/Proxy 两种形态)
- MyCat(老牌代理)
- 应用层自实现(GORM 分表插件等)
分布式事务
跨库/跨服务后,本地事务失效。常见方案按一致性强度:
| 方案 | 一致性 | 性能 | 复杂度 | 场景 |
|---|---|---|---|---|
| 2PC / XA | 强 | 差 | 中 | 传统数据库间,低频关键操作 |
| TCC(Try-Confirm-Cancel) | 最终(较实时) | 好 | 高(每步业务实现三接口) | 资金类核心链路 |
| 本地消息表 / 事务消息(RocketMQ) | 最终 | 好 | 中 | 绝大多数异步场景首选 |
| Saga | 最终 | 好 | 中(补偿链长) | 长流程 |
本地消息表(最常用)
1. 业务操作 + 写消息记录 在同一个本地事务中落库 ← 原子性靠本地事务
2. 定时任务/后台线程扫描消息表,投递 MQ,标记已发
3. 下游消费 MQ,处理成功后 ACK;失败重试
4. 对账兜底
TCC 示例(转账)
Try:冻结 100 元(可用余额 -100,冻结 +100)
Confirm:扣冻结 100(真正扣款)
Cancel:解冻(冻结 -100,可用 +100)
经验:优先用「业务上能否接受最终一致」倒推方案。能接受 → 事务消息;不能(如同一账户实时扣款)→ TCC 或合并到一个库。
异构与数据同步
分库分表后复杂查询的标配:
MySQL(分片) → Canal/Debezium(订阅 binlog) → Kafka →
├─ Elasticsearch(多维检索)
├─ ClickHouse/Doris(分析)
└─ 宽表回写 MySQL(固定报表查询)
选型建议
QPS < 5000、单表 < 500 万行:单机 + 从库 + 缓存,别折腾
读多写少:一主多从 + 读写分离
写瓶颈、数据量大:先归档冷数据;再分库分表(ShardingSphere)
运维能力不足但需要扩展:TiDB 等 NewSQL(兼容 MySQL 协议,免分库分表心智)
下一篇:运维与生产实践。
