Redis的单线程架构决定了它的读写能力存在上限。当业务量增长到一定程度,尤其读多写少的场景下(比如商品详情页、热点配置、排行榜缓存),所有请求都压在主节点上,很容易出现响应时间抖动甚至超时。这时候给Redis配置一个或多个只读副本,把读请求分流出去,是成本最低、见效最快的扩容方式。

只读副本的工作原理:主从复制是怎么发生的
Redis的主从复制分为两个阶段。第一个阶段是数据同步:从节点向主节点发送PSYNC命令,如果是初次连接,主节点会执行bgsave生成一份RDB快照发给从节点,从节点清空自身数据后载入这份快照。第二个阶段是命令传播:主节点把之后收到的每一条写命令实时发送给从节点执行,从而保持两边数据一致。
这里面有个关键设计叫复制ID。每个主节点有一个唯一的replid和对应的复制偏移量。从节点断线重连时会把上次记住的replid和offset一起发给主节点,如果主节点发现这个offset还在自己的复制积压缓冲区(replbacklog)范围内,就只补发缺失的那部分命令,这就是部分复制;否则退化为全量复制,代价会大很多。理解这一点对后面的参数调优很重要。
需要注意的是,从节点默认就是只读的,由replica-read-only参数控制,默认值为yes。千万不要为了图方便把它改成no,否则从节点上的写入既不会同步回主节点,也会在下次全量同步时被清空,属于典型的数据丢失隐患。
动手配置:搭建一主一从的读写分离环境
假设主节点运行在127.0.0.1的6379端口,我们新增一台机器跑从节点。最简单的方式是在从节点的配置文件redis.conf中加入下面这行:
# 从节点配置文件中加入 replicaof 127.0.0.1 6379 # 从节点只读(默认就是yes,显式写出来更清晰) replica-read-only yes # 建议从节点关闭持久化,减少磁盘IO,数据以主节点为准 save ""
也可以在运行时动态设置,不用重启服务:
# 在从节点的redis-cli中执行 127.0.0.1:6380> replicaof 127.0.0.1 6379 OK # 查看复制状态 127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up slave_read_repl_offset:102400
在主节点上执行info replication则能看到connected_slaves的数量。当master_link_status显示为down时,先检查网络连通性和主节点的requirepass与从节点的masterauth是否匹配,这是新手最常踩的坑。
如果读压力特别大,一台从节点不够用,可以配置多个从节点,也可以让从节点再挂从节点形成链式结构(比如A是主,B从属于A,C从属于B)。链式拓扑能减轻主节点的复制带宽压力,代价是C的数据延迟会累积得更明显。
参数调优:让复制更稳、断线恢复更快
复制积压缓冲区的大小直接决定断线后能否走部分复制。默认的repl-backlog-size只有1MB,生产环境网络抖动几秒钟就可能超出这个范围,触发代价高昂的全量复制。读写QPS高的场景建议设置为64MB甚至256MB,同时把repl-backlog-ttl设为0表示永不过期,避免无从节点时缓冲区被回收。
主从之间的心跳和超时参数也值得关注。repl-timeout默认60秒,网络环境较差的内网跨机房部署可以适当调小,让故障更快被发现。另外从节点建议开启replica-serve-stale-data no吗?这要看业务:设为yes(默认)时,从节点断线后仍会用旧数据响应读请求;设为no则直接报错。对于缓存类业务,返回稍微旧的数据通常比报错更友好,保持默认即可。
如果从节点数量很多,主节点为每个从节点维护独立的输出缓冲区会有内存压力,可以通过client-output-buffer-limit replica 256mb 64mb 60控制上限,防止某个慢从节点把主节点内存拖垮。
客户端侧的读写分离实践
服务端搭好后,还需要客户端把读请求路由到从节点。以Java生态常见的Jedis为例,借助ShardedJedis或手动维护连接池都可以实现。更推荐的做法是使用lettuce或Redisson,它们原生支持主从模式的读写分离:
// lettuce 的主从读写分离示例
RedisURI uri = RedisURI.builder()
.withHost("127.0.0.1").withPort(6379) // 主节点
.build();
StatefulRedisMasterReplicaConnection<String, String> conn =
MasterReplica.connect(client, StringCodec.UTF8, uri);
// 读请求走从节点,写请求走主节点
conn.setReadFrom(ReadFrom.REPLICA);
ReadFrom策略有多种选择:REPLICA优先读从节点,MASTER_PREFERRED优先主节点、主不可用再读从,NEAREST选择网络延迟最低的节点。根据业务对一致性的容忍度来选,不要一刀切。
必须正视的问题:复制延迟与数据不一致
读写分离天然带来一个矛盾:主从同步是异步的,客户端刚写入主节点,紧接着去从节点读,可能读到的还是旧值。对排行榜、计数器这类要求强一致的数据,读写分离并不合适,这类请求应该强制读主节点。而对商品信息、用户资料这类容忍秒级延迟的数据,从节点读取的收益远大于风险。
一个常见的折中方案是读自己的数据走主节点:用户提交修改后,短时间内该用户相关的读请求路由到主节点,过期后再回到从节点。业务代码里用一个短时间的标记位即可实现,成本很低。
另外要注意从节点故障时的故障转移问题。原生主从模式中主节点挂掉需要人工执行replicaof no one把某个从节点提升为主,自动化方案要靠哨兵(Sentinel)或集群模式。如果业务对可用性要求高,建议直接上Sentinel,它能在主节点失联时自动完成选主和客户端通知。
总结
只读副本是Redis水平扩展读能力的基础手段,核心配置只需要一行replicaof,但要真正在生产环境稳定运行,需要理解全量复制与部分复制的区别,合理设置repl-backlog-size,并根据业务一致性要求选择合适的客户端读策略。读多写少的缓存场景用它性价比极高,而对强一致敏感的数据则要谨慎评估,必要时读写都走主节点。掌握这些细节,主从架构才能真正为业务扛住流量。