mysql在高可用架构中使用主从复制实现读写分离

8次阅读

mysql主从复制仅同步数据,不自动实现读写分离;需应用层或中间件显式路由select至从库,否则所有请求仍打向主库。

mysql在高可用架构中使用主从复制实现读写分离

主从复制不是读写分离的自动开关

MySQL 主从复制本身只负责数据同步,不自带读写路由能力。应用层或中间件必须明确区分 SELECTINSERT/UPDATE/delete 请求,否则所有流量仍打向主库,从库形同虚设。

常见错误是误以为开启 CHANGE MASTER TO 后“自然就分开了”,结果监控里看到从库 Seconds_Behind_Master 持续上涨,而主库压力纹丝不动——其实是没改应用代码或没配代理。

  • 应用直连场景:需在业务代码中手动拆分连接池,写用 masterDataSource,读用 slaveDataSource
  • 使用 proxySQLMaxScale 时,必须配置 mysql_query_rules 规则,匹配 SELECT 才转发到 hostgroup 2(从库组)
  • ORM 如 mybatis + spring,容易忽略 @Transactional(readOnly = true) 不一定触发从库路由,得看具体 AbstractRoutingDataSource 实现逻辑

从库延迟导致读不到最新数据

Seconds_Behind_Master 显示为 0 并不等于强一致性。网络抖动、大事务回放、从库 IO 能力弱于主库,都会造成“伪实时”。尤其在订单创建后立刻查详情的场景,极易读到空结果。

这不是配置问题,是架构约束。解决思路不是压低延迟(极限也难到毫秒级),而是分类处理:

  • 对刚写入就必须读的请求(如“提交订单→查订单号”),强制走主库,加 /*+ FORCE_MASTER */ 注释或调用专用主库接口
  • 报表类、用户中心等非关键读,容忍几秒延迟,直接走从库
  • 避免在事务内混用主从连接——Spring 的 @Transactional 默认绑定单个数据源,跨源事务会破坏 ACID

主从切换后读请求仍在旧主库上失败

传统 MHA 或 Orchestrator 完成故障转移后,新主库 IP/端口变了,但客户端连接池还缓存着旧地址。此时 SELECT 报错 Error 2003 (HY000): Can't connect to MySQL server 或持续超时。

根本原因在于连接池无感知。应对方式取决于架构层级:

  • 应用侧:用支持动态重连的驱动(如 MySQL Connector/J 8.0+ 的 autoReconnect=true&failOverReadOnly=false),但仅缓解,不根治
  • 中间件侧:ProxySQL 支持 mysql_servers 表热更新,配合 Orchestrator 的回调脚本可自动刷新后端列表
  • dns 层:将 master.db 解析指向 VIP,VIP 漂移到新主库,要求应用全用域名连接且禁用 DNS 缓存(connectTimeout=3000 配合 cachePrepStmts=false

半同步复制不能代替高可用决策

启用 rpl_semi_sync_master_enabled=ON 只保证至少一个从库收到 binlog,不保证它已执行完。当主库崩溃,那个“已接收”的从库可能 Exec_Master_Log_Pos 还卡在中间,强行提升会导致数据丢失

真正决定能否切换的是 GTID 或位点比对:

  • SHOW SLAVE STATUSG 对比 Retrieved_Gtid_SetExecuted_Gtid_Set,差集为空才说明完全追平
  • 若用传统 binlog 文件+位置,需确认 Master_Log_File == Relay_Master_Log_File && Read_Master_Log_Pos == Exec_Master_Log_Pos
  • MHA 的 --check_repl_delay 默认关掉,否则可能因短暂延迟拒绝切换——这在高并发写入时很常见

主从复制的可靠性边界很清晰:它解决单点故障,不解决脑裂、不保证实时、不自动修复应用路由。把“能切”当成“已切好”,是线上事故最常踩的坑。

text=ZqhQzanResources