3. 缓存读写分离核心概念

读写分离是缓存架构中最基础、最有效的扩展手段。理解其底层原理——主从复制,以及分离后缓存层与持久层各自的职责变化,是设计高可用缓存系统的第一步。本章从复制原理出发,逐步拆解读写分离的定义与收益。

3.1 主从复制原理

Redis 主从复制(Replication)是读写分离的底层支撑。主节点(Master)负责处理写请求,从节点(Slave)通过异步复制同步数据,承担读流量。复制过程分为全量复制与增量复制两个阶段。

核心要点: 主从复制是异步的,主节点不等待从节点确认即返回客户端。这种设计保证了主节点的高吞吐,但也带来了秒级的数据不一致窗口。

3.1.1 全量复制流程

  1. 从节点发起 PSYNC:从节点向主节点发送 PSYNC ? -1,请求全量同步。
  2. 主节点生成 RDB:主节点执行 bgsave 生成快照,同时将新写入命令缓存到复制缓冲区(repl_backlog)。
  3. 传输 RDB:主节点将 RDB 文件发送给从节点,从节点清空旧数据并加载 RDB。
  4. 发送缓冲区增量:主节点将复制缓冲区中积压的写命令发送给从节点,从节点执行这些命令追上最新状态。
  5. 建立长连接:后续主节点持续将写命令以 REPLCONF ACK 机制推送给从节点,进入增量复制阶段。

3.1.2 增量复制与部分重同步

当网络闪断后重新连接,从节点发送 PSYNC <runid> <offset>,主节点检查复制偏移量是否仍在 repl_backlog 范围内。若在,则只发送缺失的增量数据;若不在,则回退到全量复制。

复制类型 触发条件 数据量 对主节点影响
全量复制 首次连接、offset 过期、runid 不匹配 全量 RDB + 缓冲区增量 CPU/IO 开销大,可能阻塞
增量复制 网络闪断后 offset 仍在 backlog 内 仅缺失的写命令 几乎无影响
生产建议: 设置合理的 repl-backlog-size(默认 1MB,建议 64MB~256MB),增大增量复制的命中率,避免频繁全量复制导致主节点抖动。
Redis 主从复制流程 主节点 (Master) 写请求入口 从节点 (Slave) 读请求入口 ① PSYNC ? -1 ② bgsave 生成 RDB repl_backlog 缓冲区 ③ 传输 RDB 文件 ④ 发送缓冲区增量命令 清空旧数据 + 加载 RDB 执行增量命令追平 ⑤ 长连接持续推送写命令 全量复制仅在首次或 offset 过期时触发 正常运行时为增量复制,开销极小

3.2 读写分离的定义

读写分离是指将缓存系统的写操作(SET、DEL、EXPIRE 等)路由到主节点,将读操作(GET、MGET、EXISTS 等)路由到从节点。架构上形成“一主多从”的拓扑,从节点水平扩展以承载更高的读 QPS。

关键约束: 读写分离不适用于对一致性要求极高的场景。由于复制延迟,从节点可能读到旧数据。业务需评估“读脏数据”的容忍度。

3.2.1 典型拓扑

  • 一主一从:主节点写,从节点读,适合读多写少的入门场景。
  • 一主多从:主节点写,多个从节点分担读流量,适合读 QPS 万级以上。
  • 树形复制:从节点再挂载子从节点,适合跨机房或网络延迟较大的场景。

3.2.2 客户端路由策略

策略 实现方式 优点 缺点
客户端直连 SDK 内维护主从列表,写走主、读走从 延迟低,无中间件开销 客户端逻辑复杂,主从切换需感知
代理层路由 Twemproxy / Codis / Redis Sentinel 代理 客户端无感知,运维统一 增加一跳,延迟略高
读写分离 SDK Jedis / Lettuce 的读写分离模式 配置简单,社区成熟 需依赖 SDK 版本支持
缓存读写分离架构 应用客户端 写请求 (SET/DEL) 读请求 (GET/MGET) 主节点 (Master) 写 + 读(可选) 从节点 1 (Slave) 只读 从节点 2 (Slave) 只读 异步复制 异步复制 写请求全部路由到主节点,读请求分散到多个从节点,实现读写流量分离

3.3 缓存层与持久层分离的收益

读写分离不仅是在 Redis 主从之间分配流量,更本质的收益在于将“缓存层”与“持久层”的职责彻底解耦。在传统架构中,缓存和数据库往往混用同一组资源;分离后,每一层都可以独立优化。

3.3.1 收益对比

维度 未分离(混用) 分离后(缓存层 + 持久层)
资源隔离 缓存与数据库争抢 CPU/内存/网络 缓存层独立部署,互不干扰
扩展性 扩展数据库节点成本高、复杂度大 缓存层可快速水平扩展从节点
延迟 数据库查询延迟高(ms 级) 缓存层内存访问(μs 级)
可用性 数据库故障直接导致服务不可用 缓存层故障可降级到持久层,不丢数据
成本 数据库 SSD 成本高,扩容昂贵 缓存层使用内存,按需扩容,性价比高

3.3.2 典型分层架构

  • 缓存层(Redis 主从集群):承载 90% 以上的读流量,响应时间 < 1ms,数据可丢失(从持久层重建)。
  • 持久层(MySQL / PostgreSQL):承载写流量与少量读流量,保证数据最终一致性,响应时间 5~50ms。
  • 回源策略:缓存未命中时,从持久层加载数据并回写缓存;缓存层故障时,直接降级到持久层。
常见误区: 不要将 Redis 当作数据库使用。即使开启 AOF/RDB 持久化,Redis 仍然是缓存语义——数据可能因内存淘汰、主从切换、节点宕机而丢失。持久层才是数据的最终归宿。

3.3.3 分离后的接口契约变化

引入读写分离后,接口契约需要明确区分“读一致性级别”:

// 强读:必须读到最新数据,直接走主节点
GET /api/user/:id?consistency=strong

// 弱读:允许读到秒级延迟数据,走从节点
GET /api/user/:id?consistency=eventual

// 写操作:始终走主节点,同步写持久层
POST /api/user/:id  { "name": "Alice" }

这种契约设计让业务方根据场景选择一致性级别,既保证了核心场景的准确性,又充分利用了从节点的读能力。