读写分离与主从延迟

单库顶不住读流量时,最省事的一招是把读请求分给从库:主库专心写,从库负责读。代价是“主库写成功、从库还没同步”的那段时间里,读到的是旧数据。本章讲清读写分离怎么落地,以及主从延迟带来的不一致怎么收窄。

为什么要读写分离

  • 读多写少的业务(商品详情、列表页、报表)里读请求占 80%~95%,复制几台从库就能线性扩展读能力。
  • 主库只承担写,锁竞争和 IO 压力都小,写性能更稳定。
  • 从库可以单独用于备份、离线统计、数据分析,不影响线上主库。

主从复制是怎么工作的

以 MySQL 为例,核心是 binlog:

主库:  写事务 → 写 binlog
从库:  IO 线程拉 binlog 写入本地 relay log → SQL 线程回放 relay log 应用到从库数据

按确认时机可分为几种模式:

模式主库何时返回成功特点
异步复制写完 binlog 立即返回延迟最小、性能最好;主库宕机可能丢最后一个事务
半同步复制至少一个从库收到 binlog 后返回减少丢数据风险,延迟随网络增加
全同步/组复制多数派应用后返回一致性最强,吞吐与复杂度代价大

主从延迟从哪来

来源说明缓解手段
从库回放慢早期从库 SQL 线程单线程回放开启并行复制、binlog 用 ROW 格式
大事务、大 DDL一次更新百万行,回放要很久拆批量、错峰执行、用在线 DDL 工具
从库配置差从库机器/磁盘比主库弱从库硬件不低于主库
从库跑重查询报表、全表统计抢占资源报表走专用从库
网络与跨机房跨机房复制 RTT 高同机房优先,跨机房做单独拓扑

延迟通常是毫秒级,但在批量写、大促、DDL 期间可能拉长到秒级甚至分钟级。

读写分离的三种落地方式

方式做法优点缺点
中间件用 ShardingSphere、MyCat 之类代理应用零改动多一层代理,运维与排查成本高
应用层多数据源自己封装路由 + 动态数据源灵活可控每个写点都要注意走主库
注解/框架路由@Transactional(readOnly = true) 之类提示路由代码干净强依赖框架约定,易误判

以中间件配置示意(字段以官方文档为准):

# 读写分离规则示意
rules:
  - !READWRITE_SPLITTING
    dataSources:
      readwrite_ds:
        writeDataSourceName: master
        readDataSourceNames: [slave0, slave1]
        loadBalancerName: round_robin

“写后读”不一致的解法

下单成功页面立刻展示订单,若这次读被路由到从库,就可能查不到刚写的订单。常见三种解法:

  1. 强制走主库:写操作后的一段时间内,该用户的读请求走主库。实现上给会话打标记:
// 以下片段需放进使用数据源路由的工程中运行
public void afterWrite(String userId) {
    // 往缓存写一个短命标记,读路由发现标记存在就走主库
    redis.setex("route:master:" + userId, 3, "1"); // 3 秒内该用户读主库
}

public String route(String userId) {
    return redis.exists("route:master:" + userId) ? "master" : "slave";
}
  1. 延迟窗口:对时间特别敏感的业务(如支付结果),干脆把整个读接口按“写后 N 秒读主库”的规则配置化,而不是逐处判断。
  2. 会话粘滞:同一用户在会话期内固定路由到主库,实现简单,但主库读压力下不来,只适合读量很小的管理后台。

选择原则:只对“必须读到最新”的接口加主库路由,其他读接口继续走从库,否则读写分离就白做了。

监控主从延迟

不能等用户报错才发现延迟,要主动监控:

# MySQL 中查看复制状态(8.0.22 起也提供等价的 SHOW REPLICA STATUS)
mysql -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master"
# 输出示例:Slave_IO_Running: Yes / Slave_SQL_Running: Yes / Seconds_Behind_Master: 0

关键指标与坑:

指标含义注意
Seconds_Behind_Master从库回放滞后秒数可能显示 0 但实际滞后,不宜作为唯一依据
位点差Read_Master_Log_PosExec_Master_Log_Pos 之差更灵敏,可换算成字节积压
心跳表延迟主库定时写时间戳,从库读差值业界常用做法,最可靠
复制线程状态IO / SQL 线程是否 Yes出现 No 要立刻告警

建议阈值化告警:延迟超过 1 秒观察、超过 5 秒告警、超过 30 秒自动把敏感读切回主库。

小结:读写分离靠 binlog 复制扩读能力,代价是主从延迟;落地方式有中间件、应用层路由、注解路由三类。真正要花心思的是“写后读”场景——用强制走主库、延迟窗口或会话粘滞兜住一致性,同时用位点差和心跳表持续监控延迟。

笔记加载中…