1. 决定用 Docker 搭建 Redis 集群前,我先想清楚了这几件事
先说结论:用 Docker 搭 Redis 集群完全可行,但如果你直接按单机版思路去跑,大概率会在网络这关上翻车。我自己就是从“docker run 一个 redis 然后 cluster meet”起步的,结果卡了整整一个下午。这篇文章就把我完整的实操过程、踩坑记录和最终可用的方案整理出来,希望你能少走几步弯路。
先说我的实际需求。公司内部有个数据看板项目,平时读多写少,但单机 Redis 的瓶颈已经很明显了——高峰期连接数打到上限,偶尔还会出现 key 过期淘汰导致的延迟抖动。我想在测试环境搭一套真实的 Redis Cluster,先把故障转移、槽位重分配这些机制摸透,再考虑迁移到生产。考虑到手头就一台 16G 内存的服务器,直接跑三个虚拟机有点浪费,Docker 容器是最轻量的选择。
但在动手之前,有几个关键点必须掰清楚。
第一,Docker 容器里的 Redis 集群和物理机部署有什么区别?
物理机部署时,每个 Redis 实例直接用宿主机 IP 和固定端口通信。而 Docker 容器的 IP 是动态的,重启后会变化,而且默认网络模式下容器之间的通信要依赖端口映射或自定义网络。Redis Cluster 的节点之间需要通过 gossip 协议互相发现和交换状态,如果容器 IP 变化,整个集群的节点信息就乱了。
第二,Redis 集群的节点发现机制决定了端口必须“内外一致”。
Redis Cluster 的每个节点会向其他节点通告自己的 IP 和端口。如果容器内 Redis 监听的端口和映射到宿主机上的端口不一致,集群在握手时会拿不到正确的节点信息。比如你在容器内让 Redis 监听 7001,但映射到宿主机是 17001,那么其他节点收到的是“172.17.0.x:7001”,这个地址在集群外根本访问不通。
第三,广播地址(cluster-announce-ip)是 Docker 部署的生命线。
好在 Redis 从 3.2.0 版本开始提供了cluster-announce-ip、cluster-announce-port、cluster-announce-bus-port三个配置项,专门用来处理 NAT 环境或 Docker 端口映射的场景。这套配置告诉集群:“虽然我在容器里,但请用我指定的 IP 和端口来访问我”。这也是我能继续往下推进的关键。
所以,如果你也想在 Docker 下搭 Redis 集群,第一步不是急着写 docker-compose,而是把网络模型想清楚:你是准备让容器直接用宿主机网络模式(--network host),还是用自定义 bridge 网络 + 固定 IP,或者用端口映射 + announce 配置。三种方案我都试过,下面会逐个说结论。
2. 方案选型:三种网络模式我都试了一遍
在做任何部署之前,我觉得最值得对比的就是怎么让容器网络和 Redis Cluster 的通信模型匹配。这个选择决定了后面所有步骤的复杂度,也决定了集群的稳定性。
2.1 方案一:host 网络模式,最简单但最容易出问题
docker run --network host的意思是容器直接用宿主机的网络栈。这样容器的 Redis 监听端口就是宿主机端口,Redis 集群节点间通信用的也是宿主机 IP,完全回到“物理机部署”的逻辑,配置最少。
听起来很美好,但问题在于:一个宿主机端口只能被一个容器占用。也就是说,如果你在三台宿主机上各部署两个 Redis 节点,那没问题。但如果在一台机器上部署六个容器节点,host 模式下端口冲突直接让你连启动都做不到。你可以让不同容器监听不同端口来解决,比如 7001-7006,但这样你就退回到“手动管理端口”的模式,docker 的资源隔离优势大打折扣。
还有一个实际运营上的坑:团队里如果有人习惯用 IDE 或客户端去连 Redis,host 模式下所有端口直接暴露在宿主机网络上,没有一层 NAT 隔离,安全性相对弱一些。当然这取决于你的运维规范,这里不展开。
2.2 方案二:bridge 网络 + 固定 IP,最像物理机部署
既然 host 模式端口冲突,那我一开始想的是给每个容器分配一个自定义网络里的固定 IP。
docker network create redis-cluster-net --subnet=172.20.0.0/16然后为每个容器指定--ip 172.20.0.2、--ip 172.20.0.3这样的地址。这样做的优点是:容器 IP 固定,Redis 的bind和cluster-announce-ip都可以直接写这个 IP,节点间通信非常稳定。你不需要做端口映射,因为自定义网络内的容器之间可以直接通过固定 IP 访问。
但这个方案有个隐藏的问题:如果你需要从宿主机外部(比如你的开发机)访问这个集群,就得给每个容器做端口映射。一旦做端口映射,端口内外不一致的问题又回来了,你还是得靠cluster-announce-port去补救。而且每个容器都要先设置固定 IP 再映射端口,docker-compose 文件会非常啰嗦。
我后来测试下来的感受是:固定 IP 方案最接近物理机部署,链路干净,适合你对 Docker 网络比较熟、能接受手动配置复杂度的场景。
2.3 方案三:端口映射 + cluster-announce 配置,最终我用的方案
最后我选用的是最符合日常 Docker 使用习惯的方案:每个容器映射不同端口到宿主机,然后用cluster-announce-ip等参数把集群间通信的“对外身份”固定下来。
具体的端口规划如下表:
| 节点角色 | 容器名称 | Redis 端口 | Cluster Bus 端口 | 宿主机映射端口 |
|---|---|---|---|---|
| master-1 | redis-master-1 | 7001 | 17001 | 7001:7001 |
| master-2 | redis-master-2 | 7002 | 17002 | 7002:7002 |
| master-3 | redis-master-3 | 7003 | 17003 | 7003:7003 |
| slave-1 | redis-slave-1 | 7004 | 17004 | 7004:7004 |
| slave-2 | redis-slave-2 | 7005 | 17005 | 7005:7005 |
| slave-3 | redis-slave-3 | 7006 | 17006 | 7006:7006 |
这里解释一下 Cluster Bus 端口:Redis Cluster 节点之间除了常规的客户端通信端口,还有一个额外的总线端口用于集群内部消息(握手、心跳、故障检测)。它的默认规则是主端口 + 10000。也就是说 Redis 端口是 7001,总线端口就是 17001。在 Docker 环境里,如果你不改这个参数,容器内的 17001 和宿主机映射的 17001 也要对上。所以我在实际配置里显式写了cluster-announce-bus-port,保证总线消息能穿透容器网络。
最终我的启动参数核心部分长这样:
docker run -d \ --name redis-master-1 \ -p 7001:7001 \ -p 17001:17001 \ -v /data/redis/7001:/data \ redis:7.0.12 \ redis-server \ --port 7001 \ --cluster-enabled yes \ --cluster-config-file nodes-7001.conf \ --cluster-node-timeout 5000 \ --appendonly yes \ --cluster-announce-ip 192.168.31.88 \ --cluster-announce-port 7001 \ --cluster-announce-bus-port 17001那这个192.168.31.88是什么?就是宿主机的局域网 IP。因为端口映射之后,外部访问的入口是宿主机 IP,所以我让集群里所有节点都通告宿主机 IP 和对应的映射端口。这样无论容器内部 IP 怎么变,只要我没动启动参数,集群成员之间的“联系方式”就永远是稳定的宿主机 IP:端口。
注意:如果你的宿主机 IP 会变(比如 DHCP 分配),那
cluster-announce-ip也要跟着改。为了避免这个问题,生产建议用固定 IP 或专门的 DNS 名称。
3. 哨兵模式 vs 集群模式:为什么我不选择“主从 + 哨兵”
动手之前我还纠结过一个点:到底是搭传统的“Redis 主从 + Sentinel 哨兵”,还是直接上 Redis Cluster?这两个方案很常见,网上相关的观点也比较多,热词里也经常出现“redis 哨兵模式和集群模式的区别”。这里我必须先做个对比,因为选错了方向后面全白干。
3.1 哨兵模式适合什么场景
哨兵模式解决的核心问题是自动故障转移。你有一个主节点、一个或多个从节点,Sentinel 进程持续监控主从状态。一旦主节点挂了,Sentinel 会选举一个新的主节点,然后把从节点重新指向新主节点。客户端通过 Sentinel 获取当前主节点地址,实现高可用。
但它有两个明显的限制:第一,所有的数据仍然只在一个主节点上写入,主节点的写能力就是整个系统的上限。第二,Sentinel 本身只是监控和切换,不会帮你做数据分片,内存上限还是单机的物理内存上限。
如果你只是想解决“Redis 挂了自动切换”的问题,数据量不大、并发写不高,那哨兵模式完全够用,而且运维复杂度比 Cluster 低不少。
3.2 集群模式的关键差异
Redis Cluster 不只是高可用,它加上了数据分片。整个集群的数据被分成 16384 个哈希槽,每个主节点负责一部分槽位。当你写入某个 key 时,客户端通过对 key 做 CRC16 运算,得到一个槽位编号,然后跳转到负责该槽位的节点读写。
这样带来的好处是:多个主节点可以同时承担写请求,整体容量和吞吐量可以横向扩展。当你需要扩容时,把一部分槽位从旧节点迁移到新节点,就完成了数据重分布。同时,每个主节点还带从节点,一旦主节点挂了,它的从节点会被提升为新主节点,保证槽位始终有服务。
这个模式的缺点也很明显:运维复杂度高,客户端必须支持集群协议,处理 MOVED 重定向、ASK 重定向等异常。有些老项目的客户端库并不方便开启集群模式。另外,如果你只有一个 key 需要高频读写,集群并不会让这个 key 的性能变好,因为单个 key 只落在固定的槽位和节点上。
3.3 我的选择逻辑
回到我的场景:数据看板项目里有大量的用户行为指标,天然适合按用户 ID 或业务类型分片。随着业务增长,我需要的不只是高可用,还有可扩展的写能力和更大的总内存。所以最终决定用 Redis Cluster,而且是 3 主 3 从的经典结构。
这里额外说一句,很多教程会直接让读者用redis-cli --cluster create一句命令创建集群,但那是基于单机所有节点在同一个网络环境下的假设。在 Docker 环境里,我强烈建议你先手动把六个容器的网络连通性验证好了,再执行集群初始化,否则失败后排查起来会非常痛苦。
4. 环境准备与六节点启动:一步步把容器跑起来
在实际操作之前,先把基础环境说明白。我用的宿主机是 Linux,Docker 版本 24.0 以上,镜像版本选了redis:7.0.12。选择这个版本的原因是 7.x 版本对 Cluster 的支持比较成熟,原生的集群管理命令也比 5.0 时代友好很多。
4.1 关闭防火墙限制与确认端口可用
这一步看起来简单,但特别容易忽略。Docker 容器端口映射之后,宿主机防火墙如果拦住了对应端口,集群节点之间就无法通信。
# 检查端口是否被占用 ss -lntp | grep -E '7001|17001'如果端口被占,先释放或者换个端口。我的习惯是用7001-7006加对应的17001-17006作为集群预留端口段,既好记也方便后续维护。
4.2 用 one by one 方式启动六个容器
我刚开始用 docker-compose 一键启动六个服务,确实省事,但出了问题很难定位是哪个容器起晚了导致集群初始化失败。后来改成按顺序逐个启动,并在启动后检查日志确认 Redis 是否正常进入 cluster 模式。
下面是我为 master-1 准备的详细启动命令(其他节点类似,只需替换端口和目录):
docker run -d \ --name redis-master-1 \ --restart unless-stopped \ -p 7001:7001 \ -p 17001:17001 \ -v /data/redis/7001:/data \ redis:7.0.12 \ redis-server \ --port 7001 \ --cluster-enabled yes \ --cluster-config-file nodes-7001.conf \ --cluster-node-timeout 5000 \ --appendonly yes \ --appendfsync everysec \ --save 900 1 \ --save 300 10 \ --save 60 10000 \ --cluster-announce-ip 192.168.31.88 \ --cluster-announce-port 7001 \ --cluster-announce-bus-port 17001参数说明一下:
--cluster-enabled yes:开启集群模式--cluster-config-file:集群节点配置文件。默认会在/data目录下生成--cluster-node-timeout 5000:节点超时时间,单位毫秒。超过这个时间没收到心跳就认为节点有故障--appendonly yes+--appendfsync everysec:开启 AOF 持久化,每秒刷盘,兼顾可靠性和性能--save系列:开启 RDB 快照策略,配合 AOF 双保险--cluster-announce-*:前面反复强调的网络通告配置
其他五个节点的命令完全一致,把端口、目录、容器名替换掉即可。比如 master-2:
docker run -d \ --name redis-master-2 \ --restart unless-stopped \ -p 7002:7002 \ -p 17002:17002 \ -v /data/redis/7002:/data \ redis:7.0.12 \ redis-server \ --port 7002 \ --cluster-enabled yes \ --cluster-config-file nodes-7002.conf \ --cluster-node-timeout 5000 \ --appendonly yes \ --appendfsync everysec \ --save 900 1 \ --save 300 10 \ --save 60 10000 \ --cluster-announce-ip 192.168.31.88 \ --cluster-announce-port 7002 \ --cluster-announce-bus-port 170024.3 启动后检查容器状态
六个容器都启动之后,不要急着创建集群,先确认每个容器的 Redis 进程状态和监听端口。
docker ps | grep redis-在宿主机上执行:
redis-cli -p 7001 cluster info如果返回cluster_state:fail是正常的,因为集群还没初始化。但至少说明端口是通的,Redis 进程已经进入集群模式。这一步验证很重要,可以提前发现端口映射或配置问题,而不是等创建集群时一起报错。
4.4 关于 Docker Desktop 与 Windows/Mac 环境的一点提醒
热搜词里有“docker desktop 安装”“virtualization support not detected 无法启动”这些,说明不少朋友的 Docker 环境本身就有问题。
如果你用的是 Docker Desktop,注意两点:第一,Windows 下要把 Docker Desktop 的 WSL2 后端打开,否则容器网络会走 Hyper-V 的 NAT 模式,内部 IP 段比较特殊,和 Redis Cluster 的通告机制容易冲突。第二,Mac 下 Docker 的网络模式是虚拟化的,容器之间是互通,但外部访问要依赖端口映射,所以我的cluster-announce-ip方案在 Mac 上同样适用。
如果你的 Docker Desktop 一直报 Virtualization support 相关错误,通常是 BIOS 里没开启虚拟化。这个属于环境问题,网上相关资料也很多,解决完再回来继续。
5. 集群初始化的两种方式:官方一键脚本与手动 meet
六个容器都准备好了,接下来就是最核心的一步:把六个节点组成一个集群。这里有两个路线可以走,我两个都试过,结果开发效率和排错体验差异很大。
5.1 方式一:redis-cli --cluster create 一条命令完成
如果你用的是 redis:7.x 镜像,可以直接在一台能访问宿主机映射端口的机器上执行:
redis-cli --cluster create \ 192.168.31.88:7001 \ 192.168.31.88:7002 \ 192.168.31.88:7003 \ 192.168.31.88:7004 \ 192.168.31.88:7005 \ 192.168.31.88:7006 \ --cluster-replicas 1这个命令会自动做主从分配、槽位分配,交互式询问是否接受分配计划,输入yes就完成。整个过程一气呵成。
但很多人在这一步会卡住,因为这条命令要求执行端能访问到所有节点的 Redis 端口和 Cluster Bus 端口。如果redis-cli工具本身不在宿主机上,而是放在容器里,那容器访问宿主机映射端口时可能会有 NAT 差异。
我当时的做法是:直接在本机安装一个 redis-tools 包来执行集群初始化命令。因为宿主机访问自己映射的端口最直接,不会有容器间通信的干扰。
# Ubuntu/Debian apt-get install -y redis-tools # CentOS/Rocky dnf install -y redis注意,CentOS 系列默认包名叫redis,安装后也会带上redis-cli。
5.2 方式二:手动 cluster meet,逐个节点添加
如果你不想用一键脚本,或者你用的 Redis 版本比较老(5.0 之前),就需要手动方式:
redis-cli -p 7001 cluster meet 192.168.31.88 7002 redis-cli -p 7001 cluster meet 192.168.31.88 7003 ...这样每个节点会逐步被7001节点认识,并最终形成全互联的集群拓扑。但手动方式有个麻烦:它不会自动分配槽位,也不会自动分配从节点。你得手动给每个主节点分配槽位范围,手动给主节点指定从节点,非常繁琐。
我的建议是:除非你在排查故障或者测试特殊拓扑,否则直接用redis-cli --cluster create,它内部会自动做节点握手、槽位分配、从节点配对,比手动方式可靠得多。
5.3 初始化完成后立即验证
集群创建成功后,用下面的命令快速验证:
redis-cli -p 7001 cluster info redis-cli -p 7001 cluster nodes redis-cli -p 7001 cluster slotscluster info里cluster_state:ok,cluster_nodes能看到 6 个节点,cluster_slots显示 16384 个槽位全部分配完成,说明集群已经可用了。
这里说一个小细节:cluster nodes输出的每行格式是“节点ID 地址:端口 角色 状态 槽位”,地址部分显示的应该都是192.168.31.88:700X。如果还显示172.17.0.x:700X之类的容器内部 IP,说明cluster-announce-ip没有生效,集群内部通讯会有问题,即使当前能 work,容器重启后大概率会失联。
6. 故障转移实测:我直接 kill 掉一个主节点
集群搭好之后,不做一次真实的故障转移测试,就相当于写完了代码没跑测试。这里我完整记录一次主节点宕机后集群的响应过程,以及我观察到的现象和注意事项。
6.1 选定测试主节点并准备观测指标
我的三个主节点分别是 7001、7002、7003。为了不影响测试环境下别的业务,我选择了 master-3(7003)作为故障对象。先记录下它当前从节点是哪个,以及它负责的槽位范围:
redis-cli -p 7003 cluster nodes | grep master输出中应该能看出 7003 的从节点是哪个。也可以直接看cluster slots命令,它会列出每个槽范围对应的主节点和从节点。
6.2 模拟主节点宕机:直接停掉容器
docker stop redis-master-3这样比在容器里执行redis-cli shutdown更接近真实的物理宕机场景:进程被强制终止,不会优雅地通知其他节点。
然后每隔几秒检查一次集群状态:
redis-cli -p 7001 cluster info第一次(几秒内)可能看到cluster_state:fail,这是正常的,因为cluster-node-timeout设置为 5000 毫秒,其他节点发现 7003 失联需要一些时间。
大概 5 到 10 秒之后,再执行:
redis-cli -p 7001 cluster nodes你会看到原来 7003 的从节点状态从slave变成了master,并且它接管了原来 7003 负责的所有槽位。同时整个集群的cluster_state恢复为ok。
6.3 写入数据验证主从切换
用 redis-cli 连接集群,并写入一个 key:
redis-cli -c -p 7001 set test-key hello redis-cli -c -p 7001 get test-key-c参数会让客户端自动跟随集群重定向。如果写入和读取都成功,说明集群的槽位服务已经由新的主节点接管,数据链路没有问题。
如果集群分片逻辑正确,这个 key 会落到某个槽位。这个槽位原来的主人是 7003,现在则由 7003 的从节点(新的主节点)提供读写服务。
6.4 恢复宕机节点并观察其角色变化
故障测试完成后,把刚才停掉的容器重新启动:
docker start redis-master-3容器启动后,Redis 进程起来,它会尝试重新加入集群。因为此时原本它负责的槽位已经被它的从节点接管,它会发现自己的主节点身份已经没了,于是自动以从节点的身份挂到新的主节点下面,继续做数据同步。经过测试,即使容器 IP 有变化,由于我们用的是宿主机 IP 通告,节点重新加入集群依然能正常握手。
这里有个容易忽略的点:容器重启后,AOF 和 RDB 持久化文件还在/data目录里,节点会基于这些文件恢复数据。所以放心重启即可,不必担心数据丢失。
7. 日常维护:集群重启、扩容与槽位迁移注意事项
集群能用只是开始,真正考验人的是后面的日常维护。这一节是我在实际操作里最有价值的部分,因为很多细节不跑一遍根本不会知道。
7.1 容器重启顺序:从节点优先,主节点确认角色
如果你的宿主机重启了,所有容器都会跟着起来。由于 Docker 本身的启动顺序不保证,可能会出现六个容器同时启动,但彼此还没完全就绪的情况。
我的做法是,在宿主机启动后,写一个简单的启动脚本,按顺序依次启动容器:
docker start redis-slave-1 redis-slave-2 redis-slave-3 sleep 10 docker start redis-master-1 redis-master-2 redis-master-3先启动从节点,再启动主节点,可以避免由于主节点先启动后立即尝试联系从节点而捉急等待超时的情况。当然,即使启动顺序不理想,Redis 集群的节点发现机制也能在后续通过心跳逐步恢复,只是等待时间会拉长。为了省事,我还是建议保持这个顺序。
启动完成后,执行cluster info,确认cluster_state:ok。如果某个节点丢了自己的节点文件(比如/data目录被清了),它可能无法重新加入集群,这时候最简单的方法就是重新加入,甚至重建节点,具体在故障排查里单独说。
7.2 扩容:新增一个主节点并迁移槽位
测试集群规模不够的时候,可能想加节点。Redis Cluster 支持动态扩容,但流程比创建集群稍微复杂一点。
第一步,先启动一个新容器作为新节点,比如 7007:
docker run -d \ --name redis-master-4 \ -p 7007:7007 \ -p 17007:17007 \ -v /data/redis/7007:/data \ redis:7.0.12 \ redis-server \ --port 7007 \ --cluster-enabled yes \ --cluster-config-file nodes-7007.conf \ --cluster-node-timeout 5000 \ --appendonly yes \ --cluster-announce-ip 192.168.31.88 \ --cluster-announce-port 7007 \ --cluster-announce-bus-port 17007第二步,把新节点加入集群:
redis-cli -p 7001 cluster meet 192.168.31.88 7007此时 7007 在集群里,但还没有分配任何槽位。你现在看cluster nodes,它显示master但是槽位为空。
第三步,把一部分槽位迁移过去:
redis-cli --cluster reshard 192.168.31.88:7001这个命令会交互式问你迁移多少个槽位、把槽位迁到哪个节点 ID、从哪些源节点取槽。比如我想从三个旧主节点各迁 500 个槽,一共 1500 个槽给新节点,就在提示How many slots do you want to move时输入 1500,在What is the receiving node ID时输入 7007 的节点 ID,在Source node时依次输入三个旧节点的 ID,最后输入done。
7.3 缩容和节点下线:先把槽位搬走,再 remove 节点
扩容讲完,顺便说一下缩容。下线一个节点前,你必须先把它的槽位搬到其他节点,否则集群会处于异常状态。操作用reshard,只是方向是“从下线节点搬出槽位到别的节点”。
搬完空槽后,再从集群里移除这个节点:
redis-cli --cluster del-node 192.168.31.88:7001 <node-id-of-down-node>最后停掉并删除容器:
docker stop redis-master-4 && docker rm redis-master-4这套流程做完,集群规模又回到了原来的样子,业务无感知。我自己当时还验证过:迁移槽位过程中,读写不会中断,Redis 会在客户端收到 MOVED 错误后自动重定向,只要客户端支持集群模式即可。
7.4 槽位迁移期间的性能提醒
在扩容或缩容期间,迁移槽位会涉及到大量 key 的序列化传输和删除操作。如果你的集群在线业务繁忙,建议在低峰期操作。另外,迁移时单个大 key 会导致迁移时间较长,甚至阻塞源节点。所以如果有特别大的 value,最好提前拆分或规划好槽位,避免迁移时性能抖动。
8. 高频故障排查清单:从端口不通到集群恢复
这部分是我踩坑和排错最多的地方,直接列一个排查清单,方便你对照。
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
cluster_state:fail | 某个节点失联,或者槽位没有完全分配 | redis-cli -p 7001 cluster nodes查看节点状态 | 检查对应容器是否存活,kill 恢复或重新加入集群 |
| 节点无法通信 | 防火墙阻断端口,或容器内部 IP 不一致 | 在宿主机执行nc -vz 192.168.31.88 7001;在容器内redis-cli -p 7001 ping | 调整防火墙,或检查cluster-announce-ip配置 |
| 创建集群时提示节点存在 | 旧的nodes-*.conf里记录了过期节点信息 | 查看/data/nodes-*.conf内容 | 删除旧配置文件后重启容器 |
| 写入 key 报 MOVED | 客户端没开集群模式 | 检查客户端配置,使用支持 cluster 的客户端列表 | 修改客户端参数 |
| 容器重启后节点角色变化 | 主节点数据落后或从节点接管了槽位 | cluster nodes查看角色变化 | 如果不想要新角色,可以手动cluster failover切换回来 |
| Redis 容器启动失败端口被占用 | 宿主机端口被其他进程占用 | `ss -lntp | grep 7001` |
8.1 最常见的问题:容器重启后节点“失忆”
我在实际测试中发现,如果你在创建集群之后修改过容器的端口映射,或者清空了/data目录,重启容器后节点虽然使用相同的cluster-announce-ip,但可能因为持久化文件丢失而找不到原来的集群。
解决方法是:不要轻易清空/data目录。如果确实丢了,最简单的方式是重新把它作为新节点加入集群。对主节点来说,加回来时如果槽位已经被其他节点接管,它会自动变成从节点或单纯的空主节点;对从节点来说,重新加入后会触发全量同步。
8.2 Docker 网络不通时的排查链路
热词里也有“docker 网络不通”这个词,说明这确实是大家的痛点。我遇到过一种情况:容器之间能 ping 通,但 Redis 集群握手超时。
问题出在docker0网桥的 iptables 规则上。当你使用端口映射时,Docker 会在 iptables 里加规则,但这些规则偶尔会和宿主机的自定义防火墙规则冲突。
此时的排查顺序是:先看容器日志docker logs redis-master-1,再看宿主机防火墙firewall-cmd --list-all或ufw status,最后通过tcpdump -i any port 17001抓包看集群总线的消息是否到达宿主机。
如果发现总线端口被防火墙拦截,放行即可:
firewall-cmd --permanent --add-port=7001-7006/tcp firewall-cmd --permanent --add-port=17001-17006/tcp firewall-cmd --reload8.3 一点建议:容器编排别急着上 Kubernetes
顺着热词里出现“kubernetes 基于 docker 的高可用集群安装”之类的说法,我多说一句。如果你只是想在测试环境快速验证 Redis Cluster,直接在 Docker 里手动操作完全够用,不用一上来就上 Kubernetes。等你把 Docker 方案跑通了,对 Redis Cluster 的节点发现、槽位迁移、故障恢复都有了直观理解,再考虑容器编排会更顺。
我自己后续也在梳理如何用 Docker Compose 把这套方案固化下来,方便团队其他人一键拉起,但核心网络参数配置和这套手动方案一致。如果你需要给别人交付,建议把cluster-announce-ip写成变量,避免每台机器都改一遍配置。
9. 写在最后:Docker 化 Redis 集群的几个实用体会
这趟实操下来,我最深的感受是:Redis Cluster 本身并不难,难的是环境网络模型和一些隐藏约定。只要你理解了端口、总线、通告 IP 这三者的关系,后面所有步骤都是水到渠成。
再分享一个小技巧:创建集群之前,先在宿主机上用redis-cli -p 7001 ping逐个验证端口连通性,然后再用redis-cli --cluster create。这一步虽然简单,但能帮你把“端口不通”和“集群配置错误”两类问题分开处理,避免一次踩两个坑。
最后提醒一句:如果你的生产环境网络策略比较严格,或者未来有跨机房部署的需求,Redis Cluster 的节点发现机制会带来额外的运维负担。可以考虑用 Kubernetes 环境下的 Operator 方案来管理,或者使用云厂商托管的 Redis 集群服务。但不管用哪种方式,本文里这套 Docker 验证和排错思路,对你理解底层原理一定有帮助。至少我自己现在对 Redis Cluster 的机制算是彻底摸透了。