4. 从单机到主从复制:读写分离的引入,解决单点故障与读扩展问题
说实话,单机架构在早期确实够用。但业务量一上来,问题就暴露了。
我记得有一次线上告警,数据库CPU直接飙到95%。查了半天,发现是某个报表查询把整个库拖死了。所有读写请求都挤在同一台机器上,谁也跑不掉。更可怕的是——如果这台机器挂了,整个系统就瘫痪了。
这就是单机架构的两个致命伤:单点故障和读扩展瓶颈。
怎么破?主从复制 + 读写分离。
4.1 主从复制的核心原理
说白了,就是让一台数据库(主库)负责写,多台数据库(从库)负责读。主库把数据变更同步给从库。
我习惯用MySQL的binlog机制来理解这个过程:
主库操作流程:
1. 客户端发起写请求(INSERT/UPDATE/DELETE)
2. 主库执行SQL,写入binlog
3. 从库的I/O线程拉取binlog,写入relay log
4. 从库的SQL线程回放relay log,完成数据同步
嗯,这里要注意:主从复制是异步的。也就是说,主库写完就返回客户端了,不等待从库确认。这带来了性能优势,但也埋了个坑——数据延迟。
核心要点:
- 主库:负责所有写操作,也处理部分读操作
- 从库:只负责读操作,不处理写
- 同步方式:异步复制(默认)、半同步复制、全同步复制
4.2 读写分离的架构设计
你想想看,读写分离本质上就是流量拆分。把写流量引到主库,读流量分散到从库。
我在项目中遇到过一种典型的实现方式——在应用层做路由:
// 伪代码示例:读写分离路由
public class DataSourceRouter {
private DataSource master; // 主库
private List<DataSource> slaves; // 从库列表
public Connection getConnection(boolean isWrite) {
if (isWrite) {
return master.getConnection();
} else {
// 从库负载均衡,我用的是轮询
int index = atomicCounter.incrementAndGet() % slaves.size();
return slaves.get(index).getConnection();
}
}
}
这段代码看起来简单,但实际落地时坑不少。我曾经踩过一个坑——事务内的读操作必须路由到主库。为什么?因为事务内的写还没提交,从库根本看不到。如果读操作走了从库,就会读到脏数据。
避坑指南:
我曾经在项目中遇到一个诡异bug:用户下单后查不到订单。排查了半天,发现是事务内的查询走了从库,而主库的binlog还没同步过去。解决方案很简单——事务内的所有SQL都强制走主库。
4.3 主从复制的三种模式
不同的业务场景,对数据一致性的要求不一样。我一般这样选型:
| 模式 | 原理 | 一致性 | 性能 | 适用场景 |
|---|---|---|---|---|
| 异步复制 | 主库写完就返回,不等待从库 | 最终一致 | 最高 | 读多写少,可接受秒级延迟 |
| 半同步复制 | 主库等待至少一个从库确认 | 较强一致 | 中等 | 金融交易、订单系统 |
| 全同步复制 | 主库等待所有从库确认 | 强一致 | 最低 | 极少使用,性能代价太大 |
我个人习惯:默认用异步复制,对一致性要求高的场景切半同步。全同步复制?说实话,我几乎没用过,性能损耗太大了。
4.4 解决单点故障:主库高可用
读写分离解决了读扩展问题,但主库还是单点。主库挂了怎么办?
常见的方案是主从切换。我经历过一次手动切换,那叫一个手忙脚乱:
- 检测到主库宕机
- 选择一个从库,提升为主库
- 修改应用配置,指向新主库
- 重启应用
整个过程至少几分钟,业务完全不可用。后来我引入了MHA(Master High Availability),实现了自动切换:
MHA自动切换流程:
1. 监控节点检测到主库不可用
2. 选举新主库(数据最完整的从库)
3. 其他从库重新指向新主库
4. 虚拟IP漂移到新主库
5. 应用无感知,秒级完成
注意:
自动切换不是万能的。我曾经遇到过脑裂问题——主库没完全挂,只是网络分区了。这时候两个节点都认为自己是主库,数据就乱套了。解决方案是引入仲裁节点或租约机制。
4.5 读扩展:从库的横向扩展
读压力大了怎么办?加从库啊。
我习惯用一主多从的架构。比如一个主库带三个从库,读流量均匀分散。但要注意:从库不是越多越好。
为什么?因为每个从库都要从主库拉取binlog。从库太多,主库的网络带宽和磁盘I/O会成为瓶颈。我见过一个极端案例——主库挂了50个从库,结果主库的网卡被打满了,正常业务都受影响。
我的经验是:一个主库带3-5个从库比较合理。如果还需要更多读能力,可以考虑引入缓存层(比如Redis)或者做数据库分片。
4.6 架构图:主从复制与读写分离
下面这张图是我自己画的,展示了主从复制+读写分离的完整架构:
4.7 实践中的几个关键问题
最后,分享几个我在实战中遇到的坑:
- 数据延迟监控:我习惯在从库上监控seconds_behind_master指标。超过阈值就报警,把读流量切回主库。
- 从库故障处理:某个从库挂了,自动从负载均衡列表中摘除。修复后再加回来。
- 主从数据一致性校验:定期用pt-table-checksum工具检查主从数据是否一致。不一致就修复。
总结一下:
主从复制+读写分离,解决了单点故障和读扩展两个核心问题。但它不是银弹——数据延迟、主从切换、脑裂等问题都需要认真对待。下一层,我们会聊到更复杂的分布式架构。
公众号:蓝海资料掘金营,微信deep3321