3. 缓存读写分离核心概念
读写分离是缓存架构中最基础、最有效的扩展手段。理解其底层原理——主从复制,以及分离后缓存层与持久层各自的职责变化,是设计高可用缓存系统的第一步。本章从复制原理出发,逐步拆解读写分离的定义与收益。
3.1 主从复制原理
Redis 主从复制(Replication)是读写分离的底层支撑。主节点(Master)负责处理写请求,从节点(Slave)通过异步复制同步数据,承担读流量。复制过程分为全量复制与增量复制两个阶段。
核心要点: 主从复制是异步的,主节点不等待从节点确认即返回客户端。这种设计保证了主节点的高吞吐,但也带来了秒级的数据不一致窗口。
3.1.1 全量复制流程
- 从节点发起 PSYNC:从节点向主节点发送
PSYNC ? -1,请求全量同步。 - 主节点生成 RDB:主节点执行
bgsave生成快照,同时将新写入命令缓存到复制缓冲区(repl_backlog)。 - 传输 RDB:主节点将 RDB 文件发送给从节点,从节点清空旧数据并加载 RDB。
- 发送缓冲区增量:主节点将复制缓冲区中积压的写命令发送给从节点,从节点执行这些命令追上最新状态。
- 建立长连接:后续主节点持续将写命令以
REPLCONF ACK机制推送给从节点,进入增量复制阶段。
3.1.2 增量复制与部分重同步
当网络闪断后重新连接,从节点发送 PSYNC <runid> <offset>,主节点检查复制偏移量是否仍在 repl_backlog 范围内。若在,则只发送缺失的增量数据;若不在,则回退到全量复制。
| 复制类型 | 触发条件 | 数据量 | 对主节点影响 |
|---|---|---|---|
| 全量复制 | 首次连接、offset 过期、runid 不匹配 | 全量 RDB + 缓冲区增量 | CPU/IO 开销大,可能阻塞 |
| 增量复制 | 网络闪断后 offset 仍在 backlog 内 | 仅缺失的写命令 | 几乎无影响 |
生产建议: 设置合理的
repl-backlog-size(默认 1MB,建议 64MB~256MB),增大增量复制的命中率,避免频繁全量复制导致主节点抖动。
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 版本支持 |
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" }
这种契约设计让业务方根据场景选择一致性级别,既保证了核心场景的准确性,又充分利用了从节点的读能力。