搭建Redis主从复制,很多人第一反应是去下载Redis、配redis.conf、再跑两个实例,来回折腾半天。后来我换了思路,所有实例都跑在Docker里,通过docker-compose一次性把三个节点拉起来,配置全部用volume挂进去,改配置、重启、看日志都方便得多,整个过程从零到一主二从跑通,十分钟内搞定。如果你也正在用Docker做本地开发或测试环境,想在Redis主从复制这件事上少踩坑,这篇内容值得你看完。
先简单说下这次要做什么:用docker-compose在本地启动三个Redis实例,一个主节点、两个从节点,通过配置让两个从节点自动同步主节点的数据。搭建完成后,我会演示写入数据后从节点是否同步、模拟主节点挂掉后从节点如何处理,以及一些我实际踩过的问题和排查思路。
1. 方案设计:为什么用Docker而不是直接装多个Redis实例
1.1 Docker在这个场景里的价值
如果不用Docker,想在本地搭一个一主二从的Redis环境,你需要下载Redis源码或安装包,编译安装好三份,或者解压三份目录,然后分别维护三份redis.conf,还要处理端口占用的冲突、数据目录的隔离、进程的管理和日志收集。听起来工作量不大,但每换一台机器、每换一个版本就要重来一遍,尤其当你需要模拟多节点环境来做测试时,这种重复劳动非常消耗耐心。
用Docker之后,情况完全变了。Redis镜像本身就是官方维护好的运行环境,你不需要关心编译参数、依赖库、系统兼容性。三个容器可以通过同一个compose文件声明出来,端口映射、数据卷、网络、启动命令全部在文件里一次性定义清楚。以后想从一主二从扩展到一主三从,或者换一个Redis大版本,改几行配置重新执行一条命令就行,整个环境“可复制、可丢弃、可重建”,这对开发调试和测试验证来说价值非常大.
1.2 主从复制到底解决了什么问题
在拆解具体配置之前,有必要把主从复制的功能边界说清楚,否则很容易对它产生不切实际的期待。
Redis主从复制最核心的能力是数据冗余和多副本读。主节点负责接收写请求,然后把写操作以数据流的形式同步给所有从节点。这样即使主节点所在机器出现磁盘损坏、进程崩溃等极端情况,数据仍然保存在从节点上。而从节点因为拥有完整的数据副本,可以承担读请求,起到读写分离的效果——把读的流量分发到从节点,主节点专注于处理写请求,这在“读多写少”的业务场景下效果非常明显。
但主从复制不等于高可用。默认配置下,主节点宕机后从节点不会自动升级为新的主节点,业务依然会面临写入失败的问题。要实现自动故障转移,需要在主从复制的基础上再引入哨兵机制或Redis Cluster集群模式,这是另一套复杂的架构设计。我在文中后面会专门演示手动模拟故障的过程,帮你看清主从复制的边界到底在哪里。
1.3 一主二从的节点规划
我这次搭建的拓扑是一主二从,具体如下:
| 节点角色 | 容器服务名 | 映射端口 | 容器内部端口 |
|---|---|---|---|
| 主节点 | redis-master | 6379 | 6379 |
| 从节点1 | redis-slave1 | 6380 | 6379 |
| 从节点2 | redis-slave2 | 6381 | 6379 |
选一主二从而不是“一主一从”,主要考虑到两个测试场景:一是验证多个从节点同时同步是否正常;二是演示当其中一个从节点挂掉重启后,能不能自动追上主节点的数据。这些场景在生产环境里很常见,提前在本地把行为摸透,总比在线上慌张排查要好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与镜像选型
2.1 Docker环境检查
开始操作前,先确认Docker已经正确安装并启动。在终端执行:
bash复制docker version
正常情况下,输出中应该同时包含Client和Server两段信息,并且Server段的Status字段为“running”。如果只看到Client信息或者提示无法连接,说明Docker守护进程没有跑起来,需要先处理Docker环境问题。
如果你是Windows用户,启动Docker Desktop时遇到“virtualization support not detected”或“virtualisation support wasn't detected”之类的报错,大概率是BIOS里的虚拟化技术没有开启,或Windows的Hyper-V、WSL2功能未启用。这类环境问题不解决,后面所有容器操作都跑不起来。修好之后再回来,继续本次实践。
这里有个实践中的经验:在本地做Redis主从复制实验时,不需要在Docker里额外配置网络模式,用compose默认创建的bridge网络就够用。默认网络能让同一个compose文件中的容器通过“服务名+内部端口”互相访问,这个特性后面配置主从关系时会频繁用到。
2.2 Redis镜像版本选择
镜像标签直接决定了Redis的行为差异。我这次使用Redis 7.0以上的镜像做验证,配置方式和老版本有细微差别。
yaml复制image: redis:7.2
拉取最新版本的镜像:
bash复制docker pull redis:7.2
镜像本身就包含了一个默认配置的Redis服务端,直接用redis-server启动就能跑。但我们做主从复制需要自定义配置,所以要把自己的redis.conf挂载进容器里,用指定配置文件的方式启动。
bash复制docker run --rm -v /path/to/redis.conf:/etc/redis/redis.conf redis:7.2 redis-server /etc/redis/redis.conf
这里有个小知识点:redis:7.2镜像的默认工作目录是/data,默认启动命令是redis-server。我们在compose文件里覆盖command,改成redis-server /etc/redis/redis.conf,让Redis加载我们自己的配置文件。
2.3 准备工作目录结构
我的习惯是先把整个项目的目录骨架建好,再写compose文件,这样逻辑更清晰。目录如下:
text复制redis-replication/
├── docker-compose.yml
├── master/
│ └── redis.conf
├── slave1/
│ └── redis.conf
└── slave2/
└── redis.conf
创建目录:
bash复制mkdir -p redis-replication/{master,slave1,slave2}
在master、slave1、slave2三个子目录里分别放各自的redis.conf,每个节点的配置文件可以针对角色的不同做差异化设置,这样可以让你更直观地理解“同一份配置如何通过不同的参数产生不同的行为”。
3. 三个Redis节点的配置文件详解
3.1 主节点配置
主节点的配置文件放master/redis.conf,内容如下:
conf复制# 监听所有网络接口
bind 0.0.0.0
# 保护模式关闭,允许容器外部访问
protected-mode no
# 端口
port 6379
# 后台运行改为前台运行,否则Docker容器会直接退出
daemonize no
# 开启AOF持久化
appendonly yes
# 设置访问密码(可选但推荐)
requirepass redis123
逐个解释这些配置的含义。
bind 0.0.0.0是让Redis监听容器内的所有网络接口,这样从节点和宿主机都能访问到它。如果保持默认的bind 127.0.0.1,从节点将无法通过网络连接过来。
protected-mode no同样是为了允许跨容器访问。Redis默认的保护模式只接受本机回环地址的连接,在Docker这样的隔离网络环境里必须显式关闭,否则会收到“DENIED Redis is running in protected mode”的报错。
daemonize no这一项非常关键。Docker容器要求前台必须有一个常驻进程,如果Redis以守护进程方式在后台运行,容器会认为任务执行完毕直接退出。我们看到的“容器启动了立刻停止”的诡异现象,多数是这一个参数导致的。所以容器里跑Redis,千万不要开daemonize。
appendonly yes开启AOF持久化。主从复制提供的是实时的数据同步能力,但Redis重启后主节点自身的持久化策略决定了它能否恢复历史数据。开发和测试环境建议开启AOF,数据更安全。
requirepass redis123设置主节点访问密码。从节点复制时如果主节点有密码,从节点必须配置对应密码才能完成认证。我会在后面从节点的配置里演示对应的masterauth参数。
3.2 从节点配置
两个从节点的配置基本一致,区别在于服务名和端口不同。以slave1/redis.conf为例:
conf复制bind 0.0.0.0
protected-mode no
port 6379
daemonize no
appendonly yes
# 声明自己是某个主节点的从节点
replicaof redis-master 6379
# 主节点访问密码
masterauth redis123
# 从节点访问密码
requirepass redis123
核心是replicaof这一行。它告诉Redis实例:启动后主动连接redis-master这个主机名的6379端口,并把自己变成对方的从节点。
这里要注意主机名的写法。compose文件里如果定义了服务名redis-master,那么在该网络内的容器之间就能直接用这个服务名互相访问,不需要写死IP地址。这样做的好处是,即使未来重新创建容器导致IP变化,主从关系依然成立,因为服务名是固定的。
masterauth用来说明“当我去连接主节点时,用这个密码做认证”。这个参数很多人容易漏掉——只给主节点设置了requirepass,却忘了在从节点配置masterauth,结果从节点日志里不断报错“NOAUTH Authentication required”,主从关系始终建立不起来。
3.3 从节点是否要开启只读
Redis从节点默认就是只读的,即从节点上不允许执行写命令(诸如SET、LPUSH这类写入操作会被拒绝并报错)。这个默认值很合理,目的是保证数据流向的单向性,避免从节点出现与主节点不一致的数据改动。
但有特殊场景需要关闭只读,比如你想在从节点上临时执行一些统计类计算,把结果写回缓存。不过这种情况非常罕见,而且容易造成数据不一致,生产环境中强烈不建议这样做。测试环境就保持默认即可。
3.4 配置文件权限和格式的检查清单
这个细节不显眼,但遇到的概率还挺高:redis.conf里的内容如果格式有问题,Redis启动时可能直接忽略某些配置,或者干脆启动失败。我建议在启动容器之前,先在宿主机上检查一遍配置文件内容是否完整:
bash复制# 检查三份配置文件里是否包含以下关键行
grep -E "replicaof|masterauth|requirepass|daemonize" master/redis.conf slave1/redis.conf slave2/redis.conf
确认后,再继续后续步骤。这种“先检查再启动”的习惯能帮你省下不少排错时间。
4. docker-compose编排:声明式定义整个环境
4.1 compose文件结构
在项目根目录创建docker-compose.yml:
yaml复制version: "3.8"
services:
redis-master:
image: redis:7.2
container_name: redis-master
command: ["redis-server", "/etc/redis/redis.conf"]
ports:
- "6379:6379"
volumes:
- ./master/redis.conf:/etc/redis/redis.conf
networks:
- redis-net
redis-slave1:
image: redis:7.2
container_name: redis-slave1
command: ["redis-server", "/etc/redis/redis.conf"]
ports:
- "6380:6379"
volumes:
- ./slave1/redis.conf:/etc/redis/redis.conf
depends_on:
- redis-master
networks:
- redis-net
redis-slave2:
image: redis:7.2
container_name: redis-slave2
command: ["redis-server", "/etc/redis/redis.conf"]
ports:
- "6381:6379"
volumes:
- ./slave2/redis.conf:/etc/redis/redis.conf
depends_on:
- redis-master
networks:
- redis-net
networks:
redis-net:
driver: bridge
4.2 关键配置项的原理说明
ports的写法"6380:6379"表示把宿主机的6380端口映射到容器的6379端口。虽然三个容器的内部端口都是6379,但因为宿主机端口不同,我们依然可以通过6379、6380、6381三个端口在宿主机上分别访问到主节点和两个从节点。
volumes把宿主机的配置文件挂载为容器内的/etc/redis/redis.conf。配置文件在宿主机上改完后,重启容器即可生效,免去了进入容器改动文件的麻烦。团队的协作复制成本也更低,新同事拿到整个目录直接docker compose up -d,环境就有了。
depends_on控制容器的启动顺序:先启动主节点,再启动从节点。但要注意,这个配置只保证“启动顺序有先后”,不保证“主节点已经完全准备好”。实际上由于Redis启动非常快,真实场景中主从节点之间通常没有明显的时间差问题。
networks让所有容器加入同一个自定义bridge网络redis-net。在同一个网络里,容器之间可以通过服务名互相通信,这是replicaof redis-master 6379能生效的前提。
4.3 启动服务的正确姿势
在项目根目录执行:
bash复制docker compose up -d
然后确认三个容器的状态:
bash复制docker compose ps
正常的输出中,三个容器的状态都应该是“Up”。如果某个容器反复重启(“Restarting”)或直接退出(“Exited”),就需要查看对应容器的日志来定位问题。
bash复制docker logs redis-slave1
日志里如果出现“MASTER <-> REPLICA sync started”这种字样,说明从节点已经开始尝试和主节点做数据同步了。
5. 实操验证:真的在主从复制了吗
5.1 验证主从关系是否建立
在宿主机上直接用Redis命令行工具或任一客户端工具分别连接三个节点。先看主节点的从节点列表:
bash复制redis-cli -p 6379 -a redis123 info replication
输入密码的警告信息可以暂时忽略。重点看Replication这一节:
text复制# Replication
role:master
connected_slaves:2
slave0:ip=172.x.x.x,port=6379,state=online,offset=...
slave1:ip=172.x.x.x,port=6379,state=online,offset=...
role:master表明当前节点是主节点,connected_slaves:2表示已经有两个从节点连接上来,并且状态都是online。如果从节点没有成功连接,这里会显示0。
再连接任意一个从节点查看角色:
bash复制redis-cli -p 6380 -a redis123 info replication
输出中role:slave,同时master_host指向redis-master,master_link_status:up代表主从链接正常。
5.2 写入数据验证同步
在主节点写入一些数据:
bash复制redis-cli -p 6379 -a redis123
127.0.0.1:6379> SET user:name "zhangsan"
OK
127.0.0.1:6379> SET user:age 28
OK
然后在从节点查询:
bash复制redis-cli -p 6380 -a redis123 GET user:name
redis-cli -p 6380 -a redis123 GET user:age
如果返回了与主节点写入相同的值,说明数据同步正常。这一步验证的是“写主读从”的基本工作流,也是后面做读写分离时需要依赖的核心能力。
顺手再验证一下从节点是否真的只读:
bash复制redis-cli -p 6380 -a redis123
127.0.0.1:6380> SET read:only "test"
这里会报错,提示READONLY You can't write against a read only replica。看到这个报错不用慌,这说明从节点的只读保护是生效的。
5.3 验证增量同步:从节点中途离线再上线
主从复制过程中,从节点如果因为网络或自身原因断开连接,它不会傻乎乎地等着重连。Redis的复制机制支持断点续传,从节点重新连上主节点后,会尝试从断点继续同步,而不是全量同步一遍。
我们来模拟这个场景。先停掉redis-slave1:
bash复制docker stop redis-slave1
接着在主节点写入一批新数据:
bash复制redis-cli -p 6379 -a redis123
127.0.0.1:6379> MSET test:1 value1 test:2 value2 test:3 value3
重新启动redis-slave1:
bash复制docker start redis-slave1
等几秒后,在slave1上查询刚才新写入的键:
bash复制redis-cli -p 6380 -a redis123 GET test:1
不出意外的话,数据已经同步过来了。这说明从节点在短暂离线后重新连入主节点,并没有把全部数据再同步一遍,而是基于复制偏移量做了增量同步。这个机制保证了复制效率,节点越多时优势越明显。
5.4 模拟全量同步场景
增量同步适合“短时间离线”的场景,但如果是第一次建立主从关系,或从节点的复制积压缓冲区里已经没有离线期间的数据,就需要全量同步了。全量同步时,主节点会生成一个RDB快照发给从节点,从节点加载完毕后才能继续接收增量数据。
想观察全量同步的过程,可以先把从节点数据清空、重启,再让它重新跟随主节点,比较直接的做法是把主节点的数据写得多一点,然后让新从节点加入。观察主节点日志:
bash复制docker logs redis-master
完整的一次全量同步日志大致如下:
text复制Synchronization with replica 172.x.x.x:6379 succeeded
Full resync requested by replica
...
如果只是短暂断开后重连,看到的更多是Partial resynchronization相关的日志,这就对应了增量同步。两种同步机制配合,保证从节点任何情况下都能最终追上主节点的数据状态。
6. 故障转移演练:主节点挂了到底会发生什么
6.1 直接停止主节点容器
这是最直观的故障模拟。停止主节点:
bash复制docker stop redis-master
等几秒后,随便连一个从节点,看主从连接状态:
bash复制redis-cli -p 6380 -a redis123 info replication
输出中master_link_status:down,说明从节点已经感知到主节点不可达。此刻从节点依然可以正常响应读请求,但任何写请求都会被拒绝,因为从节点默认只读,没有新的主节点可以接收写入。
这个现象准确反映了主从复制和高可用的区别:主从复制保证数据有备份、读请求不停,但没有自动切换主节点的能力,写服务实际已经中断了。
6.2 手动完成一次故障切换
没有哨兵的情况下,只能手动完成切换。流程很简单:选一个从节点,通过命令把它提升为主节点。
连接slave1:
bash复制redis-cli -p 6380 -a redis123
127.0.0.1:6380> REPLICAOF NO ONE
OK
REPLICAOF NO ONE让这个从节点断开与主节点的复制关系,并转变为一个新的主节点。验证一下:
bash复制127.0.0.1:6380> INFO replication
角色变为role:master。此时slave1已经可以接收写入了:
bash复制127.0.0.1:6380> SET after:failover "ok"
接着把slave2重新指向新的主节点:
bash复制redis-cli -p 6381 -a redis123
127.0.0.1:6381> REPLICAOF 127.0.0.1 6380
OK
注意,这次REPLICAOF后面的地址只能写宿主机IP或服务名,不能写redis-slave1,因为slave2这个容器不一定能解析宿主机视角的“redis-slave1”这个名称,具体取决于容器网络配置。最简单稳妥的方式是直接写宿主机IP或指定容器IP。
完成上述操作后,slave2的数据会同步到新主节点的最新状态,拓扑从“一主二从”演变成了“新主+一个从”。
6.3 原主节点恢复后会发生什么
把原来的主节点重新启动:
bash复制docker start redis-master
它启动后并不会自动知道“我已经不再是主节点了”,因为在它的配置里并没有replicaof指令。它恢复后会以一个独立主节点的身份运行,里面还保留着旧数据。这时就会出现两个主节点并存的情况,两边数据可能已经不再一致。
要避免这种脑裂情景,应该在原主节点启动后,立刻把它重新指向当前的主节点:
bash复制redis-cli -p 6379 -a redis123
127.0.0.1:6379> REPLICAOF 127.0.0.1 6380
执行完后,它就会重新作为从节点同步新主节点的数据。这提醒我们:在生产环境中,如果已经有哨兵或Cluster在管理故障转移,千万别手动去重启旧主节点而不做任何处理,否则可能对数据一致性造成风险。
7. 常见问题与排查技巧实录
7.1 主从关系建立不起来,日志提示MASTER <-> REPLICA sync started
最常见的原因是网络不通或主机名解析失败。先确认从容器里能否访问主节点:
bash复制docker exec redis-slave1 redis-cli -h redis-master -p 6379 ping
如果报错或无法解析主机名,检查compose文件中网络配置是否一致。如果返回PONG,说明网络通,问题多半出在认证或权限层面,继续看下一条。
7.2 日志提示NOAUTH Authentication required
这说明主节点设置了密码,但从节点没有带对密码去认证。按本文方法,在从节点的redis.conf里配置masterauth redis123,重启容器即可解决。
7.3 容器启动后立即退出或反复重启
优先查看日志:
bash复制docker logs redis-slave1
如果日志里出现了配置错误、数据目录权限之类的提示,按提示修改。如果整个日志几乎没有任何输出,很可能是daemonize yes导致的容器退出,改成daemonize no。
7.4 从节点能同步数据,但外部工具连接不上
多半是端口映射或bind、protected-mode的问题。检查docker compose ps里的端口映射状态,再确认redis.conf中是否设置了bind 0.0.0.0和protected-mode no。宿主机无法访问容器内部服务时,优先怀疑这两个参数。
7.5 从节点数据一直跟不上主节点,延迟很高
这种情况通常发生在主节点写压力很大的场景。可以检查主从复制是否持续处于全量同步状态,也可能因为主节点的复制积压缓冲区太小,导致从节点经常断开后需要重新全量同步。这类问题在生产环境需要结合监控持续观察,本地演练阶段遇到的不多,但只要理解了机制,排障方向不会跑偏。
8. 总结与经验心得
到这里,一主二从的Redis主从复制环境已经搭建完成,并且做了读写验证、增量同步验证、全量同步验证和手动故障切换演练。
我在多次实践中有几个深刻的体会:
第一,主从复制是入门分布式缓存架构的重要一步,但不要把它和高可用混为一谈。没有哨兵或Cluster,主节点宕机后写服务依然会中断。
第二,Docker并不只是一个“帮你跑个Redis”的工具,它更大的价值在于把一整套环境用声明式文件管理起来。今天你搭了一主二从,明天想改成三主三从,或者加一个哨兵节点,改compose文件比在物理机上不断复制配置文件、改端口要快太多。
第三,排查主从问题时,第一时间去看Redis日志。官方镜像的日志都是直接输出到stdout的,docker logs就能看到主从复制的所有关键行为,从连接建立到同步完成,都会记录得清清楚楚。不要凭感觉改配置,日志永远是最可靠的第一手信息。
最后再分享一个我自己的操作习惯:每次改动配置文件后,都先在宿主机上悄悄检查一遍语法和关键参数,再重启容器。很多问题都出在配置文件的空格、缩进或漏写参数上,这一步虽然不起眼,但能帮你排除至少一半的“疑难杂症”。
如果你正要搭一套本地开发或测试用的Redis主从复制环境,建议直接照着这个过程走一遍,体验会比单纯看文档和命令清晰得多。后面如果还想深入,可以在这个基础上把Redis Sentinel加进来,让故障转移自动完成,那就是另一个值得仔细展开的话题了。
