🏗️ 架构设计
读写分离 · 分库分表 · 数据迁移与一致性 · 高可用方案
1. 读写分离的原理与挑战?
- 原理:主库负责写(binlog 同步),一或多个从库负责读,应用层/中间件路由(ShardingSphere、MyCat 或应用内 AOP)
- 核心挑战:主从延迟——刚写入主库立即读从库可能读不到
- 强制路由:对一致性要求高的操作(下单后立即查订单)走主库
- 延迟补偿:sleep 或基于时间戳判断
- 半同步复制:等待至少一个从库 ack 再返回(牺牲一点性能)
- 其他问题:从库读压力过大 → 从库集群 + 负载均衡;从库故障 → 剔除路由,重试主库
🎯 面试要点
- 读写分离解决的是读扩展,不解决写瓶颈和单库容量
- 实现层:MyBatis 拦截器按方法名(select→从库)或注解 @ReadOnly 路由
- Spring 的 AbstractRoutingDataSource 是应用层路由的经典实现
2. 什么时候需要分库分表?怎么分?
评估信号:单表数据量千万~亿级、单库写入吞吐瓶颈、连接数/IO 压力大。先考虑:索引优化、归档冷数据、缓存扛读——最后才是分库分表(成本极高)。
分法:
- 垂直分库:按业务域拆库(订单库/用户库/商品库),解决"单库连接和 IO 瓶颈"
- 垂直分表:大字段拆出去(order 主表 + order_detail 附表)
- 水平分库/分表:同一张表按分片键分散。分片策略:
- 范围分片:按 id 区间(如每月一个表)——跨片查询简单,但有热点
- 哈希分片:hash(shard_key) % N——数据均匀,但扩容麻烦(一致性哈希/虚拟槽缓解)
分片键的选择(重要)
// 分片键必须是高频查询条件,否则查询要广播所有分片
// 订单表按 user_id 分片:查"我的订单"单片命中 ✓;
// 但按 order_id 精确查 → 不知道在哪个片 → 广播查询 ✗
// 解决:把分片键编码进业务 ID(这叫「基因法」,需要自定义位分配)
// 标准雪花算法 = 1 符号位 + 41 时间戳 + 5 数据中心 + 5 机器 + 12 序列,本身不含 user_id 位
// 或用基因法:order_id 由 user_id 哈希 + 自增拼接,携带分片信息
🎯 面试要点
- 分片带来的问题:跨片事务(分布式事务)、跨片 join(冗余/应用层组装)、全局唯一 id(雪花算法/号段模式)、扩容迁移
- 中间件选型:ShardingSphere(应用内/代理两种模式)、MyCat;或自研路由层
- 迁移方案:双写 + 全量对账 + 灰度切换(详见下节)
3. 分库分表的平滑迁移方案?
- 双写:应用同时写老库和新分片库(新库失败不影响主流程,先记录补偿)
- 历史数据迁移:工具/脚本按 id 分批把老数据迁到新分片(binlog 追平或直接全量)
- 对账:抽样比对老库/新库数据一致性,补差异
- 灰度切换读:读流量按比例切到新库(10% → 50% → 100%),观察延迟和错误
- 切换写:确认稳定后切换写流量,老库保留只读一段时间兜底
🎯 面试要点
- 任何存储迁移的核心套路都是:双写 → 追平 → 对账 → 灰度 → 切换 → 兜底回退
- 迁移工具:canal(binlog 增量同步)是业界标配
4. MySQL 高可用方案有哪些?
- 主从 + 手动/脚本切换:主挂了脚本提升从库。简单,RTO 分钟级
- MHA(Master High Availability):经典方案,自动故障检测 + 提升从库 + 漂移 VIP。秒级~分钟级
- MGR(MySQL Group Replication):官方组复制(基于 Paxos 的 XCom 实现),自动故障转移。默认是单主模式(多主需显式关闭
group_replication_single_primary_mode);默认一致性级别为 EVENTUAL,要强一致读需另外配置 - 半同步复制 + 级联架构:减少数据丢失窗口(主库提交前等从库 ack)
- 云厂商 RDS 高可用(如阿里云)本质也是主备 + 自动切换 + 数据多副本
数据不丢的关键:binlog 必须完整同步到从库才返回提交成功(半同步/强同步),否则主挂 = 丢数据。因此高可用与数据安全是两件事:前者保证服务不中断,后者保证数据不丢。
🎯 面试要点
- 衡量指标:RPO(最多丢多少数据)、RTO(恢复多久)——面试讲方案先讲这两个目标
- 一主多从 + 从库备份:备份要放异地/跨机架,防止机房级故障
- 定期演练故障切换,别让方案只存在于文档