Redis 读写分离是应对高并发读场景的经典扩展方案:写请求走主库(Master),读请求分摊到多个从库(Replica),通过主从复制保证数据最终一致。当单实例的 CPU 或带宽成为瓶颈,或读多写少的业务(如商品详情、排行榜、会话缓存、热点配置)需要横向扩容时,读写分离能在不引入复杂分片的前提下,把吞吐轻松翻几倍。它也是从单机缓存迈向高可用架构最平滑的第一步。
一、为什么需要读写分离
大多数线上系统的访问模型都是“读多写少”:缓存命中率高的接口,读 QPS 往往是写的十倍甚至百倍。Redis 单线程模型虽然能扛住很高的吞吐,但一旦网络带宽、CPU 或连接数逼近上限,所有命令都会排队,整体延迟陡增。此时与其盲目升级机器,不如把读流量拆到从库——主库专心处理写和关键读,从库承担大部分读请求,性价比更高。
需要强调的是,读写分离并不适合写密集或强一致要求极高的场景。若业务每次写入后都要立刻读到最新值(如库存扣减、余额变更),从库的异步复制延迟会导致读到旧数据。这类场景应直接读主库,或改用其他一致性方案。
二、主从复制原理:数据如何同步
Redis 的复制基于异步方式,分为全量同步与增量同步两个阶段,理解它们有助于排查复制延迟与断连问题。
2.1 全量同步(RDB)
从库启动后向主库发送 PSYNC,主库执行 bgsave 生成 RDB 快照并传输,从库加载后追加快照期间的写命令缓冲。这一过程对主库有轻微 IO 与内存压力,因此建议在低峰期首次搭建复制,或提前预热从库。
2.2 增量同步(backlog)
网络闪断恢复后,只要从库偏移量仍在主库的复制积压缓冲区(repl-backlog)内,就会只补传断连期间的增量命令,无需再次全量。把 repl-backlog-size 调大(如 256MB)可显著提升断连恢复的成功率,避免大流量下频繁全量同步拖垮主库。
2.3 复制偏移与延迟
主从各自维护一个复制偏移量(master_repl_offset / slave_repl_offset)。二者之差即为从库落后字节数,除以主库写速率即可估算延迟秒数。这正是后续监控的核心指标。
三、用 Docker Compose 搭一主二从
最快速的验证方式是容器化部署。下面用 Docker 起一个主节点和两个从节点,从库通过 replicaof 指向主库。
version: "3"
services:
redis-master:
image: redis:7.2
command: ["redis-server", "--appendonly", "yes"]
ports: ["6379:6379"]
redis-replica1:
image: redis:7.2
command: ["redis-server", "--replicaof", "redis-master", "6379", "--replica-read-only", "yes"]
depends_on: [redis-master]
redis-replica2:
image: redis:7.2
command: ["redis-server", "--replicaof", "redis-master", "6379", "--replica-read-only", "yes"]
depends_on: [redis-master]
启动后进入任一从库执行 INFO replication,能看到 role:slave 与 master_link_status:up 即表示复制链路已建立。replica-read-only yes 是从库的重要安全阀门,防止误把写命令发到从库。
四、客户端读写分离:让读请求落到从库
光有主从还不够,应用层必须显式把读命令路由到从节点。以 Spring Boot + Lettuce 为例,借助 ReadFrom.REPLICA_PREFERRED 即可优先读从库,从库不可用时自动回落主库。
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStaticMasterReplicaConfiguration cfg =
new RedisStaticMasterReplicaConfiguration("master-host", 6379);
cfg.addNode("replica1-host", 6380);
cfg.addNode("replica2-host", 6381);
LettuceClientConfiguration clientCfg = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED) // 优先读从库,从库不可用时回落主库
.build();
return new LettuceConnectionFactory(cfg, clientCfg);
}
若使用 Redis Cluster 或代理(如 Twemproxy、Corvus、Redis Proxy),读写分离逻辑则下沉到中间件,应用无需感知节点拓扑。代理层还能做权重调度,把读请求按从库负载均衡分发。
五、哨兵(Sentinel):自动故障切换
主库宕机时,哨兵集群负责选一个从库晋升为新主库并通知客户端,避免人工介入。最小三哨兵配置如下。
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
其中 2 表示至少两个哨兵同意才判定主库客观下线,避免单点误判造成脑裂。生产环境建议哨兵与数据节点分开部署,且哨兵自身也应是奇数个(3 或 5),以保证选举多数派。故障切换后,客户端通过哨兵获取最新主库地址,实现无感知恢复。
六、三种扩展方案对比:该选哪一种
| 方案 | 适用场景 | 复杂度 | 一致性 |
|---|---|---|---|
| 主从 + 读写分离 | 读多写少、单库容量够 | 低 | 最终一致 |
| 哨兵高可用 | 主库宕机需自动切换 | 中 | 最终一致 |
| Cluster 分片 | 数据量/写量超单实例 | 高 | 分片内一致 |
| 代理中间件 | 不想改客户端代码 | 中 | 取决于后端 |
七、读写分离的三大坑
| 问题 | 成因 | 对策 |
|---|---|---|
| 主从延迟读到旧数据 | 异步复制,写后立刻读可能命中从库旧值 | 强一致场景走主库;或容忍短暂 stale |
| 脑裂丢写 | 主库假死被降级,原主仍接收写 | min-replicas-to-write + 哨兵 quorum 双保险 |
| 从库误写 | 业务代码把写发到从库 | replica-read-only yes 配合客户端路由 |
八、运维与监控要点
日常通过 INFO replication 与 INFO stats 观察复制偏移、延迟与命中率。建议把主从延迟(master_repl_offset 与从库 slave_repl_offset 之差)纳入监控,超过阈值告警。与 Redis 基础与缓存设计 中强调的命中率指标结合,可更早发现容量瓶颈。
redis-cli INFO replication | grep -E "role|master_link_status|slave_repl_offset|master_repl_offset"
# 主从延迟(字节)估算
redis-cli INFO replication | awk -F: '/master_repl_offset|slave_repl_offset/{print $1, $2}'
监控上建议对每个从库采集延迟曲线,并在延迟突增时联动告警;同时关注主库 connected_slaves 数量,防止从库掉线而无人察觉。可将这些指标接入 Prometheus + Grafana,与 PostgreSQL 慢查询优化 的监控体系共用一套看板。
九、总结
Redis 读写分离用最小的架构复杂度换来了可观的读扩展能力。记住三条主线:复制保证一致、客户端路由分摊读、哨兵保障高可用。当单主从仍扛不住时,再考虑 Cluster 分片或代理中间件。把它和数据库层优化等手段组合,能为读多写少的系统构建完整的性能护城河。
落地时建议先在预发环境用压测工具(如 redis-benchmark 或 hey)验证从库分担比例,确认读流量确实被分摊后再灰度到生产。同时把主从切换演练纳入季度复盘,定期手动杀掉主库验证哨兵能否在预期时间内完成故障切换,确保高可用不是纸上谈兵,而是真正经得起故障检验的能力。




