1. 先回答一个问题:一台 Redis 为什么扛不住生产环境
单机 Redis 在开发环境和本地 Demo 里很好用,启动快、命令简单、数据也能落盘。但一旦部署到生产环境,一台 Redis 实例很快会暴露三个层面的问题。
第一是可用性。Redis 默认运行在 6379 端口,进程一旦异常退出、服务器宕机或者内存碎片导致不可用,所有直接依赖 Redis 的请求都会立刻失败。最常见的是高并发请求全部打到数据库,数据库连接数被打满,整个链路雪崩。有人会说可以重启,但进程重启少则几秒,多则几十秒,这个时间窗口足够让业务层产生大量异常。
第二是读性能。Redis 本身是单线程事件循环模型,单个实例的 OPS(每秒操作数)通常在十万级,具体取决于命令复杂度、网络时延和硬件资源。真实业务里读请求往往远多于写请求,比如商品缓存、配置中心、用户会话,读占比可能高达 80% 以上。此时单实例的 CPU 会成为瓶颈,而且无法通过加内存解决,因为瓶颈在单线程处理能力。
第三是数据安全。虽然有 RDB 和 AOF 两种持久化机制,但持久化文件只存在于本机。磁盘损坏、误删除、机房故障都会导致数据无法恢复。RDB 快照触发时 fork 子进程做内存快照,在大实例上可能短暂阻塞主进程;AOF 重写也有类似消耗。如果只有一台机器,这些备份操作和高可用策略几乎没有回旋余地。
主从复制(Replication)就是为了解决这些问题出现的基础机制。它做的事情很直接:一台节点作为主节点(Master)负责处理写请求,一个或多个从节点(Replica)实时同步主节点的数据,从节点对外提供读服务。写入集中在主节点,读压力分散到从节点,主节点故障时由从节点接管数据,持久化备份也可以放到从节点执行。
这篇文章的核心主线是:理解 Redis 主从复制解决什么问题,然后手动搭建一个主从环境,观察一次完整的数据同步过程,最后掌握常见的同步故障排查方法。不建议直接跳到哨兵或集群,主从复制是这两者的基础,哨兵负责的是自动故障转移,集群解决的是数据分片,它们都建立在数据能被复制这一前提之上。
2. 主从复制到底复制了什么,又解决了什么问题
2.1 复制的是“写操作的结果”,不是存储文件
严格说,Redis 主从复制并不是把 RDB 文件或者 AOF 文件定时拷贝到从节点,而是通过一组协议保持主从数据一致。从节点初次连接主节点时,主节点会生成一份当前数据的快照(RDB)发给从节点,之后主节点再将复制期间产生的新写命令持续发给从节点。这样从节点在绝大多数时间内和主节点保存着相同的数据状态。
这种设计的好处是,复制过程对业务写入的影响很小。主节点只需要 fork 出一个子进程生成 RDB 文件,然后持续发送后续命令;从节点加载快照后继续执行增量命令,最终达到一致状态。后续的网络断开重连,也优先使用少量命令做增量同步,而不是每次都把全量数据重发一遍。
2.2 主从复制带来的三个直接收益
从工程角度看,主从复制至少带来三点变化:
- 读写分离。主节点处理写请求,从节点处理读请求。读多写少的业务可以把绝大多数读流量分散到多个从节点,降低主节点压力。
- 数据热备。即使主节点发生故障,数据已经同步到从节点,可以手动把从节点切换为主节点,或由哨兵自动完成切换,缩短业务中断时间。
- 备份与维护窗口隔离。在从节点上执行持久化、生成备份、执行大数据分析,都不会影响主节点的写入性能。
2.3 必须明白的一点:主从复制不等于高可用
很多初学 Redis 的人有个误区,以为做了主从复制,Redis 就具备故障自愈能力了。实际上主从复制只解决数据同步问题,不解决自动选主问题。如果主节点挂掉,从节点虽然有全部数据,但不会自己成为主节点,也不会自动接收写请求。默认情况下从节点是只读的,业务写入会直接报错。
要真正实现故障自动转移,必须在主从复制之上再部署哨兵(Sentinel),或者使用 Redis Cluster 自带的高可用能力。这个定位关系很重要,后面章节会继续展开。
3. 环境准备:用 Docker Compose 快速搭一个一主两从
主从复制可以在单机上通过多个 redis-server 实例模拟,也可以用 Docker Compose 快速编排。这里推荐 Docker Compose,隔离性好,配置清晰,适合作为学习环境。
3.1 环境要求
| 项目 | 要求 | 说明 |
|---|---|---|
| Docker | 20.10 以上 | 兼容新版 Compose v2 插件 |
| Docker Compose | v2 或 v1 均可 | v2 推荐直接使用docker compose命令 |
| Redis 镜像 | redis:7.0 或 redis:7.2 | 两个镜像配置语法基本一致 |
| 宿主机端口 | 6379、6380、6381 | 确保没有冲突,或用容器内网络测试 |
如果还没有安装 Docker,可以先安装 Docker Desktop 或 Linux 发行版对应 docker 包。整个搭建过程不需要独立编译 Redis,节省很多时间。
3.2 目录结构
redis-replication-lab/ ├── docker-compose.yml ├── master/ │ └── redis.conf ├── replica1/ │ └── redis.conf └── replica2/ └── redis.conf这个结构便于分别挂载不同节点的配置和数据目录。实际生产中往往还要挂载日志目录,学习环境可以先不配。
3.3 主节点配置
创建master/redis.conf:
port 6379 bind 0.0.0.0 protected-mode no appendonly yes appendfilename "appendonly.aof" dir /data主节点只需要保证允许从节点连接即可。protected-mode no是为了让容器外的从节点可以访问,本机学习环境可以这样用;生产环境必须配置密码和防火墙策略,不能直接暴露。
3.4 从节点配置
创建replica1/redis.conf:
port 6379 bind 0.0.0.0 protected-mode no replicaof redis-master 6379 replica-read-only yes appendonly yes appendfilename "appendonly.aof" dir /data再创建replica2/redis.conf,内容和 replica1 相同。replicaof redis-master 6379指定了主节点的主机名和端口。Docker Compose 中容器间通常用服务名互相访问,这里的服务名就是redis-master。
3.5 Docker Compose 编排文件
创建docker-compose.yml:
version: "3.8" services: redis-master: image: redis:7.0 container_name: redis-master command: ["redis-server", "/usr/local/etc/redis/redis.conf"] ports: - "6379:6379" volumes: - ./master/redis.conf:/usr/local/etc/redis/redis.conf - ./master/data:/data redis-replica1: image: redis:7.0 container_name: redis-replica1 depends_on: - redis-master command: ["redis-server", "/usr/local/etc/redis/redis.conf"] ports: - "6380:6379" volumes: - ./replica1/redis.conf:/usr/local/etc/redis/redis.conf - ./replica1/data:/data redis-replica2: image: redis:7.0 container_name: redis-replica2 depends_on: - redis-master command: ["redis-server", "/usr/local/etc/redis/redis.conf"] ports: - "6381:6379" volumes: - ./replica2/redis.conf:/usr/local/etc/redis/redis.conf - ./replica2/data:/datacommand里的路径必须和容器内挂载路径完全一致,否则 redis-server 找不到配置。宿主机端口和容器端口要区分开,容器内统一用 6379,宿主机分别映射到 6379、6380、6381。
3.6 启动并检查复制状态
在redis-replication-lab目录下执行:
docker compose up -d docker compose ps等服务全部启动后,进入主节点容器查看复制信息:
docker exec -it redis-master redis-cli info replication预期输出类似:
role:master connected_slaves:2 slave0:ip=172.18.0.3,port=6379,state=online,offset=28,lag=0 slave1:ip=172.18.0.4,port=6379,state=online,offset=28,lag=0 master_replid:e0e5c0e7f8a3a6a7e52c975e1c46b536e0b0c8ba master_replid2:0000000000000000000000000000000000000000 master_repl_offset:28出现两个state=online的从节点,表示复制链路已经建立。如果state不是online,而是connect或sync,说明网络不通或配置没生效。
4. 主从复制的核心机制:全量同步、增量同步和心跳
搭建成功后,接下来研究底层机制。理解这些机制对排查同步延迟、断线重连和数据丢失问题非常关键。
4.1 replid 和 offset:判断传输进度的两个核心变量
每个 Redis 主节点拥有两个标识复制状态的变量:
master_replid:主节点实例的唯一复制 ID,可以理解为当前“数据历史”的版本标识。每次实例重新启动,或者执行REPLICAOF NO ONE变成主节点后,都会生成新的 replid。master_repl_offset:已处理的写命令字节数偏移量。从节点每执行完一条同步命令,也会向主节点汇报自己的偏移量。
主节点通过对比master_replid和偏移量来判断从节点的数据落后程度。如果从节点保存的主节点 replid 和当前主节点相同,且偏移量小于主节点偏移量,则只需要发送中间缺失的命令段。如果 replid 不同,说明从节点曾经连接过另一个主节点,或者主节点重启过,必须做一次全量同步。
4.2 全量同步流程
第一次连接或 replid 不匹配时,会发生全量同步,流程可以概括为:
- 从节点发送
PSYNC ? -1,表示不知道主节点的 replid 和偏移量,请求全量复制。 - 主节点检查 replid 后,返回
FULLRESYNC <replid> <offset>,触发子进程生成 RDB 快照。 - 主节点把 RDB 文件通过 socket 发送给从节点,同时把生成快照期间产生的新写命令缓存到复制积压缓冲区(repl_backlog_buffer)。
- 从节点清空旧数据并加载 RDB,加载完成后继续接收主节点发送的增量命令。
RDB 文件大小直接决定全量同步耗时。几 GB 的实例做全量同步,会占用大量带宽和磁盘 IO,从节点加载期间可能短暂不可用。生产环境要尽量避免频繁全量同步。
4.3 增量同步与 repl_backlog 缓冲区
网络断开重连时,如果主节点 replid 没变,且从节点的偏移量仍在主节点保存的 repl_backlog 范围内,就可以做增量同步。repl_backlog 是一个环形缓冲区,默认大小是 1MB,通过配置参数repl-backlog-size调整。
同步流程是:从节点重连后发送PSYNC <replid> <offset>,主节点检查偏移量是否落在 repl_backlog 中。如果命中,返回CONTINUE,并发送 offset 之后缺失的命令;如果没有命中,则直接触发全量同步。
这个设计解决了一个关键问题:断线时间短、写命令少时,不需要重新传输整个数据集,只需要把断线期间的命令补上。实际应用中,如果断线时间很长,超过 repl_backlog 的覆盖范围,就会退化为全量同步,这也是很多大实例同步风暴的主要原因。
4.4 主从之间的心跳检查
从节点默认每 10 秒向主节点发送一次REPLCONF ACK <offset>,主节点通过info replication中的lag字段观察最近一次通信间隔。如果 lag 值持续变大,说明主从之间的网络延迟或丢包比较严重。
主节点也会定期向从节点发送 PING,默认周期为 10 秒,通过repl-ping-replica-period配置。生产环境不建议把心跳频率调得过高,否则大量从节点会带来额外的网络包;也不建议过低,否则故障检测会变慢。
可以做一个简单实验来验证心跳:在主节点上写入数据,然后观察从节点是否能立刻读到:
docker exec -it redis-master redis-cli set mykey hello docker exec -it redis-replica1 redis-cli get mykey docker exec -it redis-replica2 redis-cli get mykey正常情况三次都会输出hello。如果从节点读取为空,说明复制链路中断或从节点还在同步中。
5. 从节点只读、读写分离和一致性边界
5.1 从节点默认只读,写操作会被拒绝
Redis 从节点默认配置replica-read-only yes,直接向从节点写入会得到:
(error) READONLY You can't write against a read only replica.这是合理的约束。如果允许从节点写入,该节点的数据会和主节点产生分歧,后续增量命令到达时要么产生覆盖,要么无法合并,最终导致数据不一致和复制中断。因此生产环境永远不要开启从节点的写入能力,除非短时间用它做特殊的数据修复。
5.2 读取从节点数据时要注意复制延迟
主从复制是异步复制,主节点执行完写命令后立即返回成功,不等从节点确认。因此在极端高负载或主从网络异常时,从节点的数据可能落后于主节点。对于一致性要求很高的读请求,例如用户刚提交订单后立即查询订单状态,从从节点读取可能读到旧数据。
应对策略有三种:
- 关键读请求强制走主节点,普通读走从节点。常见做法是在业务代码里根据读敏感度路由到不同 Redis 节点。
- 使用 WAIT 命令等待复制完成。
WAIT numreplicas timeout可以等待至少 N 个从节点确认复制完成,但这会牺牲一部分写入响应时间。 - 接受较短时间的最终一致性,适合读多写少且数据变更允许有秒级延迟的场景,比如商品详情、配置项、非核心列表数据。
5.3 从节点也可以有自己的从节点吗
可以。Redis 支持链式复制,即从节点 A 作为从节点 B 的主节点。这样做的好处是减轻主节点的推送压力,让主节点只复制给少数几个“一级从节点”,再由一级从节点转发给更下游的节点。缺点是链路变长,任何一层延迟都会传导到更下游,监控也更复杂。中小型业务规模不推荐链式复制,直接让主节点连接全部从节点即可。
6. 常见问题排查:复制链路不可能永远一帆风顺
主从复制最常见的问题集中在全量同步风暴、断线重连退化为全量同步、认证失败和主从数据延迟。下面按现象、原因、检查方式、解决方案逐一整理。
6.1 从节点一直处于 connect 状态
现象:info replication中从节点state=connect或state=connecting,始终无法变为 online。
检查顺序:
docker exec -it redis-replica1 redis-cli ping docker exec -it redis-replica1 redis-cli info replication docker logs redis-replica1可能原因:
- 从节点配置中
replicaof的地址不对,或者主节点端口没开放。 - 主节点开启
protected-mode yes,但没有配置密码,也没有在 bind 列表中允许从节点 IP。 - 容器间网络不通,Docker Compose 中服务名无法解析。
- 主节点配置了
requirepass,从节点没有配置masterauth。
解决方案:
- 检查
replicaof主机名和端口是否能在容器内 ping 通。 - 如果主节点启用了密码,从节点必须增加:
masterauth yourpassword6.2 网络抖动后大量从节点做全量同步
现象:主节点网络短暂不可用后,多个从节点同时重新连接,主节点 CPU 升高,带宽打满,所有从节点开始全量同步。
原因:断线时间较长,从节点请求的偏移量已经不在 repl_backlog 缓冲区范围内。主节点只能推送 RDB 文件,导致大量带宽消耗。如果同时有多个从节点重连,就会出现“复制风暴”。
解决方案:
- 适当调大
repl-backlog-size,例如从 1MB 调到 64MB 或 128MB,让断线重连能够落在增量范围内。 - 减少从节点直接连接主节点的数量,使用链式复制(Tree Replication)分散压力。
- 在业务低峰期处理大规模重启,避免多个从节点同时冷启动。
6.3 主从数据延迟越来越大
现象:info replication显示从节点 lag 值不断增大,业务读取从节点总是拿到旧数据。
检查方式:
- 查看主从之间网络延迟和丢包率,用
ping、redis-cli --latency观察。 - 查看主节点是否执行了大量阻塞性命令,例如
KEYS *、HGETALL大 key 或SORT大集合。 - 检查从节点是否在同时执行 RDB 快照或 AOF 重写,导致从节点处理命令缓慢。
解决方案:
- 避免在主节点执行阻塞命令,改用
SCAN系列命令遍历 key。 - 把持久化操作放到从节点执行,但要注意从节点性能。
- 如果业务对一致性要求高,可以引入 WAIT 或让关键读请求走主节点。
6.4 主节点挂掉后想手动切换到从节点
现象:主节点 ip 不可用,希望立刻把某个从节点提升为新主节点继续写入。
操作方式:
docker exec -it redis-replica1 redis-cli REPLICAOF NO ONE执行后这个节点会成为新的主节点,开始接收写请求。其他从节点需要重新指向新主节点:
docker exec -it redis-replica2 redis-cli REPLICAOF redis-replica1 6379这里直接写容器服务名是因为仍处于 Docker 网络内。如果宿主机访问,要使用映射后的 IP 和端口。
生产环境不建议手动执行这套操作,而应该交给 Sentinl 自动完成。手动操作容易忘记切换客户端连接配置,而且难以保证数据不丢失。新主节点没有同步到的最后写命令,在重建主从后可能永久丢失。
6.5 主从复制常见问题速查表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 从节点 connect,无法同步 | 网络不通、认证失败 | ping、查看日志 | 修正 replicaof,配置 masterauth |
| 全量同步频繁 | repl_backlog 太小 | 查看同步日志 | 调大 repl-backlog-size |
| 主从延迟大 | 网络差、大 key、从节点负载高 | 查看 lag、连接数 | 分散读压力、禁止大 key 操作 |
| 从节点写入报 READONLY | 只读配置 | 检查 slave-read-only | 不要直接写从节点 |
| 切换后数据丢失 | 主节点故障前数据未同步 | 对比 repl offset | 使用哨兵高可用,挂多个从节点 |
6.6 常见坑清单
坑一:把主节点配置成protected-mode no后直接暴露公网,导致 Redis 被扫描攻击。学习环境可以这样用,但生产环境必须配置bind白名单和requirepass。
坑二:从节点只配置replicaof,忘记配置masterauth。主节点本来有密码,从节点连接时被拒绝,日志里会反复出现MASTER <-> REPLICA sync started:和Authentication failed。
坑三:全量同步过程中直接查看从节点数据,发现数据不全,以为同步失败了。其实从节点加载 RDB 需要时间,加载完成前部分 key 是不存在的。等待info replication的 offset 追上主节点后再验证。
7. 从主从复制走向哨兵和集群:什么场景该用什么方案
主从复制本身已经能让 Redis 从“单点开发工具”变成“可扩展的缓存层”。但它不是终态,不同业务规模需要不同架构。
7.1 主从复制、哨兵、集群三种形态对比
| 形态 | 解决核心问题 | 自动故障转移 | 数据分片 | 适用规模 |
|---|---|---|---|---|
| 单节点 | 本地缓存 | 无 | 无 | 开发、测试 |
| 主从复制 | 读扩展、数据热备 | 无 | 无 | 中小型项目,读多写少 |
| 哨兵 + 主从 | 主节点自动故障转移 | 有 | 无 | 需要高可用,数据量在一台机器可承受 |
| Redis Cluster | 数据分片 + 高可用 | 有 | 16384 个槽位 | 数据量大,单机内存不足 |
选择依据很简单:如果一台 Redis 实例内存足够、查询量没有把单个实例压满,使用哨兵 + 主从是最稳妥的高可用方案。当内存容量超过单台服务器、单实例写入 QPS 成为瓶颈,才需要 Cluster 分片。
7.2 哨兵在主从基础上做了什么
哨兵(Sentinel)是一个独立进程,通过订阅主节点的__sentinel__:hello频道和定期 PING 来监控主从节点状态。当主节点被多数哨兵判定为主观下线,就会触发故障转移流程,自动把一个从节点提升为主节点,并通知其他从节点和老客户端更新连接目标。
在主从基础上部署哨兵,复杂度会增加,但换来了真正的自动恢复能力。实际生产环境至少需要 3 个哨兵节点,保证在形成多数派时不会出现双主脑裂。
7.3 除了高可用,还要做哪些配套
即使部署了主从和哨兵,Redis 依然需要外部配套。
- 持久化策略不能只依赖复制。复制没有落盘时,重启后数据可能为空。
- 需要监控主从延迟、内存使用、连接数和慢查询,一旦异常能及时告警。
- 客户端要支持故障转移时的重连和路由更新,否则主备切换后业务请求仍然指向旧主节点。
8. 生产环境的主从复制最佳实践
8.1 配置层面的核心建议
主节点配置主要注意:
- 开启
requirepass和masterauth,从节点必须配置相同密码。 protected-mode在生产环境保持默认的 yes,并通过bind限制来源 IP。- 根据网络状况调整
repl-backlog-size,建议从 64MB 起调,观察断线重连是否触发全量同步。 - 避免在主节点上执行
KEYS *、SMEMBERS大集合这类阻塞命令,复制进程也会受影响。
从节点配置主要注意:
- 保持
replica-read-only yes。 - 如果从节点承担持久化备份任务,要确保磁盘空间充足。
- 从节点数量不建议过多,一个主节点挂 2 到 3 个从节点通常足够。更多从节点会加重主节点的发送压力。
8.2 监控指标清单
| 监控项 | 命令/来源 | 预警条件 | 处理建议 |
|---|---|---|---|
| 复制 offset | INFO replication | offset 差距持续增大 | 检查网络和从节点负载 |
| lag | INFO replication | lag 大于 5 秒 | 检查心跳和命令执行耗时 |
| 从节点状态 | INFO replication | state 非 online | 查看日志并重建同步 |
| 内存使用 | INFO memory | used_memory 超过 maxmemory 80% | 清理过期 key 或扩容 |
| 磁盘占用 | 系统 df | 超过 80% | 清理 RDB/AOF 冗余文件 |
| 大 key 数量 | redis-cli --bigkeys | 存在超大 key | 拆分为更小 key |
8.3 发布前检查清单
- 主从节点 Redis 版本保持一致,避免命令行为和复制协议差异。
- 检查所有从节点是否都已配置
replicaof,且没有遗漏masterauth。 - 确认防火墙和安全组放行 6379/6380/6381 端口,且仅允许需要的来源 IP。
- 验证主节点挂掉后,从节点读写表现是否符合预期。
- 确认哨兵、客户端路由、监控告警都已经部署,不只是手动切换。
- 测试一次完整的数据插入和从节点读取流程,确认写入后能立刻读取到。
8.4 给新手的练习建议
先把一主一从跑通,观察INFO replication的输出变化。然后人为关闭主节点,看从节点的REPLICAOF NO ONE切换过程。接着部署两个从节点,用另一个从节点重新指向新主节点。最后再引入哨兵,观察自动故障转移的执行流程。
主从复制是最能体现 Redis 高可用设计思路的机制,把这一层弄清楚,后续学哨兵时只是增加一个监控角色,学集群时只需要额外理解槽位分配逻辑,学习曲线会平滑很多。