Redis Cluster三主三从集群部署实战:Docker环境下的高可用架构 1. 为什么是三台机器、三主三从集群规模的起点不是拍脑袋在开始动手之前得先说清楚一个很多人没想明白的问题Redis Cluster 明明单机也能跑为什么标准起步配置往往是三台机器、三主三从这不是网上教程随便抄出来的约定俗成而是 Redis Cluster 的协议机制决定的硬性下限。Redis Cluster 的槽位空间一共是 16384 个这些槽位需要由一个或多个主节点来负责。虽然官方的说法是最少 3 个主节点但真正经得起推敲的原因有两个。第一是投票机制。Redis Cluster 的故障转移依赖 Raft 风格的投票协议当一个主节点挂掉之后它的从节点想要上位必须发起一次选举收到大多数主节点的投票才能完成晋升。如果只有一个主节点或两个主节点遇到某个从节点发起选举时刚好另一个主节点也挂了这种极端情况整个集群就失去了达成 majority 的能力。三主是最小集合能保证solo 一个主节点挂掉的场景下剩下的两个主节点依然可以帮从节点完成投票。第二是从节点的价值。为什么每个主节点都要配一个从节点因为 Redis Cluster 的故障转移不是简单的主挂了就切到从而是从节点会自动晋升为主节点并且集群会把原本属于挂掉主节点的槽位重新分配给它。如果你只有三个主节点、没有从节点那任何一个主节点物理宕机对应的槽位就直接不可用了客户端访问这些槽位时会持续收到CLUSTERDOWN错误。三主三从的真实价值在于任意一台物理机挂掉集群整体依然可以对外提供服务损失的只是一部分冗余能力。三台机器的部署方式还额外规避了另一类坑如果你把所有容器都跑在一台物理机上那所谓的高可用其实是假高可用因为宿主机一断电所有 Redis 节点一起消失故障转移根本来不及发生。所以三台机器、每台机器一主一从这个组合是从数据安全、可用性和成本三个维度折中之后的最优解。另外有个细节需要提前说明三主三从不等于一台机器上跑两个容器就万事大吉。它要求的是逻辑节点分布在不同的物理机上同机的容器只互为备份不能互相形成主从关系否则失去了物理隔离的意义。下面所有操作我都假设读者手头有三台 Linux 服务器并且每台机器上已经安装了 Docker。记住节点 IP 在这个方案里是命根子后面几乎所有坑都跟 IP 有关。2. 环境准备三台服务器的Docker与网络规划2.1 Docker 安装与基础检查如果三台机器还没装 Docker优先用官方脚本处理一遍curl -fsSL https://get.docker.com | bash systemctl enable docker systemctl start docker装完之后别急着往下走先确认一个很容易被忽略的关键项版本。我一向建议 Docker 版本至少 20.10 以上因为更早的版本在容器网络和端口映射上存在一些老问题虽然跑 Redis 集群不一定触发但没必要给自己埋雷。检查命令docker --version docker compose version如果你是离线环境或者内网环境无法访问 Docker Hub提前把redis:7.0或者redis:6.2镜像拉下来导出再导入也行。但要注意镜像版本尽量统一不要三台机器各用各的版本Redis Cluster 在跨版本混用的情况下也不是不能跑只是没必要增加变量。2.2 端口规划6379 和数据总线端口缺一不可这是新手最容易翻车的地方。Redis Cluster 的每个节点实际会占用两个 TCP 端口6379客户端连接和数据交互端口。16379集群总线端口用于节点之间的心跳检测、数据迁移、故障转移消息传递。这个 16379 是 Redis 自动在业务端口上加 10000 得到的不需要你在配置里单独指定但防火墙和云安全组里必须同时放行。只放行 6379 而忽略 16379会导致集群节点之间互相通信失败表现就是cluster nodes能看到节点但握手不完整槽位永远处于fail状态。假设三台机器的内网 IP 分别是192.168.1.101、192.168.1.102、192.168.1.103。每台机器上需要放行的端口是服务器 IP业务端口集群总线端口192.168.1.101637916379192.168.1.102637916379192.168.1.103637916379如果是云服务器安全组规则里加一条 TCP 入方向源地址设为三台机器的内网网段即可如果是公司内网物理机检查 iptables 或者 firewalldfirewall-cmd --permanent --add-port6379/tcp firewall-cmd --permanent --add-port16379/tcp firewall-cmd --reload2.3 网络模式选择我为什么推荐 host 模式Docker 跑 Redis 集群通常有两种网络方案bridge网络 端口映射或者host网络直通宿主机。bridge 模式看似更干净每个容器独立 IP端口映射到宿主机但在 Redis Cluster 场景下会引入一个特别恶心的麻烦容器启动后Redis 写入 cluster 配置文件里的节点地址是它在容器内看到的 IP 和端口。如果你用端口映射容器内看到的是172.17.0.x:6379外部节点用这个地址根本连不上集群握手直接失败。虽然可以通过--cluster-announce-ip和--cluster-announce-port参数强制指定对外通告地址但这属于能跑但麻烦的方案调试起来异常痛苦。我的建议是既然是三台独立服务器部署 Redis 集群直接用 host 网络模式容器共享宿主机网络栈Redis 节点地址就是宿主机 IP干净利落。host 模式的代价是端口管理上有一定约束但 Redis 本身端口规划清晰没有那么多动态端口需求这点代价完全可以接受。2.4 目录规划配置文件和数据的落地位置虽然容器是无状态的但 Redis 的持久化数据和 cluster 状态文件必须落在宿主机磁盘上否则容器重建一次数据全丢集群状态还得重新初始化。我的目录规划如下mkdir -p /data/redis/{conf,data} chmod -R 755 /data/redisconf目录放 redis.confdata目录放appendonlydir持久化文件和nodes.conf集群状态文件。这个nodes.conf是 Redis 集群运行时自动生成的记录当前节点视角下的集群拓扑后面检查问题时会频繁用到它。3. 配置文件准备redis.conf 中决定集群能否成立的关键参数3.1 一份可以照抄的 redis.conf网上很多教程让读者用docker run参数一个个传比如--cluster-enabled yes。这种方式不是说不行但参数一多命令又长又容易漏而且不方便版本管理。我习惯把所有配置写进 redis.conf然后用挂载方式塞进容器。下面是在三台机器上可以原样使用的配置# redis.conf port 6379 bind 0.0.0.0 protected-mode no daemonize no dir /data appendonly yes appendfsync everysec # 集群配置 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-require-full-coverage no把这份配置放到三台机器的/data/redis/conf/redis.conf。注意三台机器的配置完全一样不需要按机器修改任何内容因为 Redis Cluster 的节点身份是通过运行后生成的节点 ID 区分的不依赖配置文件里的机器标识。3.2 每个参数背后的逻辑bind 0.0.0.0允许所有网卡接口上的连接进入。如果你只 bind 内网网卡 IP也可以但要保证三台机器之间能通过这个 IP 互相访问。用0.0.0.0更省事但前提是防火墙层已经做好了隔离。protected-mode no必须关掉。默认情况下 protected-mode 开启如果 Redis 没有设置密码且绑定了公网地址会拒绝非本机连接。集群节点之间是跨机器的不关掉这个选项握手阶段就会被拒绝。daemonize noDocker 容器要求前台进程运行如果设成 yes容器启动后 Redis 会 fork 到后台容器因为没有前台进程直接退出。appendonly yesappendfsync everysec开启 AOF 持久化每秒刷盘。集群模式下数据安全比单机更重要因为集群故障转移发生在节点之间如果主节点挂了但数据还没来得及持久化从节点晋升后数据会有丢失。everysec 是性能和安全的折中。cluster-enabled yes这是把单机 Redis 变成集群节点的总开关没有这个参数后续所有 join 命令都会失败。cluster-config-file nodes.conf集群状态文件。只指定文件名即可Redis 会把它写到dir指定的目录下。不要手动创建这个文件每次节点启动时会自动生成或读取它。cluster-node-timeout 5000节点超时时间单位毫秒。5000 意味着一个节点超过 5 秒没有响应集群开始判定它可能挂了并准备触发故障转移。如果你的网络环境抖动大可以放宽到 10000。cluster-require-full-coverage no这个参数极其重要。默认值是 yes表示所有 16384 个槽位都必须有节点负责集群才提供服务。把它设为 no可以让集群在部分主节点挂掉、槽位暂时无法被服务的情况下依然为其他正常的槽位提供读写。如果不设一个主节点挂了整个集群直接进入CLUSTERDOWN所有请求全部失败。3.3 关于密码的提醒生产环境一般还会加上requirepass和masterauth。但在集群模式下加密码会引入额外的复杂度主从同步、故障转移、节点间通信都必须同时使用正确的密码。如果只是快速验证集群功能第一遍可以先不加密码跑通确认架构没问题之后再设计密码方案。加密码后的完整配置我会在文章末尾补充先把基础链路打通是最重要的。4. 启动容器与创建集群从 docker run 到 cluster create4.1 三台机器同步执行 docker run配置文件都准备好之后在三台机器上分别执行同一套 docker run 命令docker run -d \ --name redis-node \ --network host \ --restart always \ -v /data/redis/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis/data:/data \ redis:7.0 \ redis-server /etc/redis/redis.conf命令拆开解释一下--network host容器复用宿主机网络栈6379 和 16379 端口直接暴露在宿主机上。-v /data/redis/conf/redis.conf:/etc/redis/redis.conf把宿主机上的配置文件挂载进去容器内 Redis 启动时读取这个路径。-v /data/redis/data:/data把宿主机数据目录挂载到容器的/data对应配置里的dir /data。redis:7.0镜像标签可以用 6.2 或 7.2但三台机器必须用同一个版本。注意这里不需要-p端口映射host 模式下端口是直通的写了反而多此一举。启动之后先看容器状态docker ps | grep redis-node docker logs redis-node --tail 50如果日志里出现Ready to accept connections说明 Redis 已经以集群模式正常启动。这时候可以再确认一下集群状态文件是否生成cat /data/redis/data/nodes.conf第一次启动时nodes.conf里只有当前这台机器的节点信息而且节点 ID 是一个类似abcdef...的 40 位十六进制字符串。每台机器一个三个文件内容各不相同。4.2 确认三台机器互相连通在三台机器上分别测试另外两台机器的服务和总线端口是否通了nc -zv 192.168.1.102 6379 nc -zv 192.168.1.102 16379 nc -zv 192.168.1.103 6379 nc -zv 192.168.1.103 16379这个测试非常重要不要在创建集群之后才发现端口不通那时候排错会非常混乱。顺便提醒一句有些云平台的 VPC 内 iptables 规则比较复杂用telnet或nc探测一次就知道问题出在哪一层。4.3 用 redis-cli 一键创建集群找三台机器中的任意一台执行创建命令一般选第一台作为操作机docker exec -it redis-node redis-cli --cluster create \ 192.168.1.101:6379 \ 192.168.1.102:6379 \ 192.168.1.103:6379 \ --cluster-replicas 1这条命令的含义是让这三台机器上的 Redis 节点组成一个集群并且为每个主节点分配一个从节点。--cluster-replicas 1就是每个主节点配一个从节点的意思因为一共 3 个主节点对应 3 个从节点正好凑成三主三从。执行之后redis-cli 会先给出一个分配方案内容大致如下 Performing hash slots allocation on 6 nodes... Master[0] - Slots 0 - 5460 Master[1] - Slots 5461 - 10922 Master[2] - Slots 10923 - 16383 Adding replica 192.168.1.102:6379 to 192.168.1.101:6379 Adding replica 192.168.1.103:6379 to 192.168.1.101:6379 ...它会把 16384 个槽位平均分成三段每台主节点分到约 5461 个槽位然后自动选择从节点的归属。在确认之前还会显示一句Can I set the above configuration? (type yes to accept)这里必须手动输入yes再回车。有部分教程说可以用echo yes |管道自动确认我试过容易出编码问题不如手动输一次。4.4 创建后的立即检查创建成功的标志是看到[OK] All 16384 slots covered这句话意味着 16384 个槽位全部有主节点负责没有空隙没有重复。看到这个输出三主三从集群就算立住了。紧接着再查一下节点状态docker exec -it redis-node redis-cli cluster nodes输出会列出 6 个节点包括每个节点的 ID、IP:端口、角色master 或 slave、连向的主节点、槽位范围。这里可以看到三台主节点各自负责一段槽位三个从节点分别挂在不同的主节点下面。5. 验证集群与故障转移三主三从到底能不能抗住一台机器挂掉5.1 基础读写验证集群建好之后先验证最基础的数据读写。注意集群模式下用redis-cli -c连接-c表示 cluster 模式会自动处理槽位跳转docker exec -it redis-node redis-cli -c然后在命令行里随便写几个 key127.0.0.1:6379 set name zhangsan - Redirected to slot [12709] located at 192.168.1.103:6379 OK 127.0.0.1:6379 get name zhangsan出现Redirected to slot表示 Redis 客户端根据 key 的 CRC16 值计算出了对应的槽位并自动把请求转发到了负责这个槽位的节点。如果不用-c参数直接访问一台节点写入一个不属于它的槽位会收到MOVED错误这是正常的不代表集群有问题。5.2 验证从节点同步随便选一个从节点直接连接上去查看数据。注意从节点默认是只读的用readonly命令可以临时允许读操作docker exec -it redis-node redis-cli -p 6379 127.0.0.1:6379 readonly OK 127.0.0.1:6379 get name zhangsan如果从节点上能读到跟主节点一样的数据说明主从复制已经建立。追查一下主从对应的关系可以通过docker exec -it redis-node redis-cli cluster nodes | grep slave输出里的第一列是从节点 ID第四列是它对应的主节点 ID最后一列是槽位信息。从节点本身没有槽位会显示为空。5.3 故障转移演练手动杀掉一个主节点现在做整篇文章里最有价值的一个实验模拟一台机器宕机看三主三从的容灾能力到底能撑到什么程度。假设我要杀掉 192.168.1.102 这台机器上的主节点。先在操作机上确认它是不是主节点docker exec -it redis-node redis-cli cluster nodes | grep 192.168.1.102正常情况下192.168.1.102 这个 IP 会出现两个节点一个是 master 一个是 slave取决于--cluster-replicas 1分配时哪个被选成主、哪个被选成从。选定真正的主节点之后直接模拟宕机。最粗暴的方式是把容器停掉docker stop redis-node然后在另一台机器上持续观察集群状态变化docker exec -it redis-node redis-cli cluster nodes一开始你会看到 192.168.1.102 的主节点仍然标记为master但状态会从connected变成disconnected。大约 5 秒之后对应配置里的cluster-node-timeout 5000状态会变成fail。再过几秒你会看到原本挂在这个主节点下面的从节点角色变成了master槽位也转移到了它头上。这个过程就是自动故障转移主节点失联之后从节点发起选举其他主节点投票通过后从节点晋升为新的主节点接管原主节点的槽位。验证一下服务是否中断docker exec -it redis-node redis-cli -c 127.0.0.1:6379 get name zhangsan只要读到的 key 是之前写入的数据说明整个故障转移过程中数据并没有丢失集群对外服务没有中断。如果你在故障切换的瞬间持续压测读写可能会在切换完成前看到个别CLUSTERDOWN或者MOVED错误但只要cluster-node-timeout设置合理这个时间窗口通常只有几秒。5.4 恢复挂掉的节点故障转移完成之后原主节点还处在停止状态。如果你想把它重新纳入集群直接重新启动容器即可docker start redis-node启动之后它会根据nodes.conf中的记录重新回到集群但此时它的身份已经变了因为原本的从节点已经晋升为主节点所以它回来之后会自动作为这个新主节点的从节点。不需要手工操作Redis Cluster 会自动完成角色转换。这就是三主三从的优雅之处整个故障转移和恢复正常的过程对客户端来说几乎透明节点增删、角色变化全部由集群内部协议处理。6. 踩坑与排查网络隔离、残留状态与集群重建6.1 网络无法互通导致的假集群我见过最多的问题是执行--cluster create时卡在Waiting for the cluster to join这一步一直转圈。这种情况 90% 是集群总线端口 16379 不通。因为 6379 端口客户端能连通新手容易忽略 16379导致节点之间的握手包发不出去。排查第一步docker exec -it redis-node redis-cli cluster nodes如果所有节点的状态都不是connected优先检查防火墙和安全组有没有放行 16379 端口。这一步常见于云服务器环境安全组规则没加高位端口。还有少数情况是 iptables 规则顺序问题明明加了放行规则却不生效用iptables -L -n查看一下规则顺序把放行规则放到拒绝规则前面。6.2 端口映射模式下的 announce-ip 问题如果你执意使用 bridge 网络 端口映射而不是 host 模式一定会遇到节点之间互相看到的 IP 是容器内网172.17.0.x的情况。此时必须在 docker run 命令里加两个参数--cluster-announce-ip 192.168.1.101 --cluster-announce-port 6379 --cluster-announce-bus-port 16379这三个参数告诉 Redis 节点对外通告的 IP 和端口是这些而不是容器运行时检测到的内网地址。三台机器各自填自己的 IP。早年间这不是什么大问题但随着 Docker 网络模式多样化这个参数成为默认操作反而是最稳妥的。6.3 重建集群时遇到 configuration conflict如果你之前创建过集群后来想推倒重来直接把容器删了再跑一遍创建命令大概率会遇到[ERR] Node 192.168.1.101:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.原因是宿主机/data/redis/data目录下的nodes.conf和 AOF 文件还残留着旧集群状态。Redis 检测到节点已经属于某个集群拒绝执行新的创建操作。解决办法是把数据目录内容清空再启动新容器rm -rf /data/redis/data/* docker rm -f redis-node docker run -d --name redis-node --network host \ -v /data/redis/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis/data:/data \ redis:7.0 redis-server /etc/redis/redis.conf三台机器都执行完清理和重启之后再执行 create 命令。这里要特别强调清理数据目录必须是三台机器同步操作不能只清理其中一台。否则残留节点会尝试加入旧集群造成节点数量对不上创建过程依然失败。6.4 槽位不均衡与手动分配正常情况下--cluster create会均匀分配槽位每台主节点分到 5461 或 5462 个。但有些场景下你可能是把已有节点动态加入集群此时槽位不会自动重新分配新主节点上槽位数量可能为 0而旧主节点的槽位数量保持不变。如果需要重新平衡槽位可以使用docker exec -it redis-node redis-cli --cluster rebalance \ 192.168.1.101:6379 --cluster-use-empty-masters --cluster-threshold 1或者手动迁移指定数量的槽位docker exec -it redis-node redis-cli --cluster reshard \ 192.168.1.101:6379 --cluster-from source-node-id \ --cluster-to target-node-id --cluster-slots 1000reshard 操作是阻塞式的期间涉及到的槽位数据会从源节点迁移到目标节点如果数据量大耗时可能会很长生产环境操作前一定确认好窗口时间。6.5 误删从节点带来的隐患加入从节点时标准命令是docker exec -it redis-node redis-cli --cluster add-node \ 192.168.1.103:6379 \ 192.168.1.101:6379 \ --cluster-slave --cluster-master-id master-node-id但如果你在操作过程中多执行了一次、或者重复添加了同一个节点集群拓扑会变得混乱。此时需要先定位异常节点docker exec -it redis-node redis-cli cluster nodes然后执行docker exec -it redis-node redis-cli cluster forget node-idforget 操作只对当前节点生效要在所有关联节点上逐个执行否则被遗忘的节点还会通过其他节点的 gossip 消息重新加入集群。这也是一个值得记住的细节Redis Cluster 的去中心化设计导致节点关系是全网广播的单点修改拓扑很容易被消息把旧状态拉回来。7. 加密码后的配置调整与日常运维提示7.1 带密码的 redis.conf 和启动参数如果要在生产环境启用密码三台机器的 redis.conf 需要增加两行requirepass YourStrongPassword masterauth YourStrongPasswordrequirepass是客户端连接时需要的密码masterauth是主从节点之间复制数据时使用的密码。两个值保持一致否则从节点无法从主节点同步数据故障转移之后新主节点也无法跟它的从节点建立复制关系。加了密码之后创建集群和日常操作命令都要加上密码参数docker exec -it redis-node redis-cli -a YourStrongPassword --cluster create \ 192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 \ --cluster-replicas 1注意-a参数会在 shell 历史记录里留下明文密码更安全的做法是使用环境变量REDISCLI_AUTHexport REDISCLI_AUTHYourStrongPassword docker exec -it redis-node redis-cli --cluster create ...此外配置了密码之后容器内执行redis-cli时少写-a会报NOAUTH Authentication required这属于正常现象不要以为是集群配置坏了。7.2 日常监控的几个关键命令集群搭起来只是开始持续稳定运行才是目标。我日常最常用的几个命令# 查看集群节点健康状态 docker exec -it redis-node redis-cli cluster nodes # 查看槽位覆盖情况 docker exec -it redis-node redis-cli cluster info # 查看每个节点的内存和连接数 docker exec -it redis-node redis-cli info memory docker exec -it redis-node redis-cli info clientscluster info里的cluster_state字段值一定是ok。如果看到fail优先看cluster_slots_pfail和cluster_slots_fail两个字段它们会告诉你当前有多少槽位处于异常状态。7.3 日志检查与容器重启策略容器日志是排查集群问题的第一手资料docker logs redis-node --tail 200如果某个节点反复崩溃多半是内存不足、maxmemory设置不当或者磁盘空间耗尽。Redis 在内存不足时会加快持久化频率导致磁盘 I/O 飙升最终节点无响应、触发其他节点的故障转移。我在配置里没有加maxmemory因为默认值是不限制内存上限Redis 会一直使用到系统内存耗尽。生产环境建议加上maxmemory 4gb maxmemory-policy allkeys-lru设置内存上限后Redis 会根据淘汰策略自动清理不常用的 key避免节点 OOM 被杀。7.4 关于扩展节点的一个建议三主三从不是终点等业务量上升你可能需要扩展到四主四从、五主五从。扩容节点时新增的主节点默认不会自动接管槽位你需要手动执行 reshard。但有个小技巧新增的从节点在 redis-cli 创建时会自动分配到槽位如果你先加从节点再执行 cluster failover 让从节点变主节点可以实现无缝接管。这也算是在实战中摸索出来的不算最优但很实用的迁移方式。另外扩容操作尽量选择业务低峰期。reshard 期间涉及的数据迁移会占用大量网络带宽和 CPU高峰期执行容易拖垮集群。写在最后的几点经验整套方案我从第一次搭到能熟练排错中间踩过不少坑。现在回看最值得记住的几条第一端口规划里一定把 16379 放进行政流程里很多生产事故都是因为这个端口被安全策略挡了半个多小时第二任何一次容器重建之前想清楚数据目录是保留还是清空这个决定直接影响集群能否正常加入第三故障转移测试敢做一次胜过读十篇文档手动杀掉一个主节点你才能直观感受到 cluster-node-timeout 到底意味着什么。三主三从的架构搭完之后日常维护其实并不复杂Redis Cluster 自我管理能力很强节点之间的心跳检测、槽位迁移、故障转移都不需要额外脚本。真正需要人工介入的场景很有限扩容缩容、异常日志排查、数据备份恢复。把这些流程都跑熟这套集群长期稳定运行基本没有悬念。