资讯详情

Docker部署Redis Cluster集群:从配置到运维的完整实操指南

📅 2026/10/20 14:35:53 | 华诺云谱 👁 阅读
Docker部署Redis Cluster集群:从配置到运维的完整实操指南
先说明一下这是一篇纯实操向的笔记不吹概念、不凑字数按我在本机某台CentOS主机Docker版本无所谓20.10以上即可反复搭过多次的流程来写。如果你也是刚接触Redis集群觉得集群哨兵可以先停一下这两者解决的问题完全不是一回事——哨兵管的是高可用把挂掉的主库扶正而真正意义上的Redis Cluster管的是数据分片把key分散到多个节点上每个节点各管一部分槽位。本文要聊的是后者。1. 为什么用Docker搭集群而不是直接装二进制先说结论Docker搭Redis集群最大的价值不是省事而是可重复。我在某台生产服务器上手动装过一次——每台机器下载、编译、改配置、开防火墙端口、写systemd unit装完三主三从部署文档写了十几页换台机器又要从头来一遍。用Docker之后整个集群的搭建过程变成了写配置 跑容器 一条命令环境差异被镜像层屏蔽掉这才是容器化最大的红利。但Docker搭集群有个天然的麻烦网络。Redis Cluster的节点之间通过gossip协议互相通信节点启动时会把自己配置的IP和端口广播给其他节点客户端也会从集群获取节点地址。如果你用默认的bridge网络容器每次重启IP都会变集群的拓扑会直接乱掉会出现一堆fail状态。所以实操中我劝你老老实实做两件事先建一个自定义的docker网络固定网段比如--subnet172.28.0.0/16这样容器的IP可控每个Redis容器用--ip显式指定IP或者干脆用--network-alias配合固定IP来用。还有一个隐藏问题Docker默认网络模式下的NAT映射。很多教程教你把6379映射到宿主机端口让容器对外通过127.0.0.1:6379访问。这在单机测试没问题但跨多台宿主机搭集群时就会翻车——因为节点注册进集群的地址是容器内的IP其他机器上的节点根本路由不到。所以用Docker搭多机集群我一般不建议用默认bridge做端口映射而是直接用host网络模式或者每台机器建相同subnet的容器网络并路由打通。下面的演示基于单机多容器用自定义bridge网络固定IP的方案这是最稳、最适合上手的。2. 环境准备网络、镜像、目录结构的一次性说明我这次用的镜像是redis:7.0.137.0之后对集群支持更成熟但不建议直接上7.2个别命令行为有变化后面细说。在开始之前先建好网络和目录骨架。docker network create redis-cluster-net --subnet 172.28.0.0/16然后创建六个节点的数据目录。这一步很多人会偷懒跳过但请相信我挂载数据目录在集群场景下不是可选项——如果不挂载容器一删这个节点在集群里的身份直接消失其他节点会把这个节点标记为fail然后触发槽位迁移生产环境这么搞基本等于事故。for i in $(seq 1 6); do mkdir -p /data/redis-cluster/node-${i} done目录结构如下目录用途/data/redis-cluster/node-{1..6}每个节点的数据持久化目录/data/redis-cluster/conf统一的配置文件目录可选/data/redis-cluster/logs日志目录可选接下来写每个节点的配置文件。以node-1为例其余节点只需改port、cluster-config-file和后续的固定IP即可。注意这里所有的坑几乎都出在配置文件里务必逐行理解。cat /data/redis-cluster/conf/node-1.conf EOF port 6391 protected-mode no daemonize no appendonly yes appendfilename appendonly.aof cluster-enabled yes cluster-config-file nodes-6391.conf cluster-node-timeout 15000 cluster-announce-ip 172.28.0.11 cluster-announce-port 6391 cluster-announce-bus-port 16391 EOF解释几个关键点port我故意不用6379默认端口六个节点依次用6391~6396。端口不冲突是一方面更重要的是cluster-announce-port必须对应真实监听端口避免混淆。protected-mode no容器里跨网络访问时如果开着保护模式非本机来源的请求会被拒绝集群节点互相通信、客户端连接都会失败。当然生产环境要靠网络ACL去兜底不能只靠Redis自己的机制。cluster-enabled yes开启集群模式没这一行后面redis-cli --cluster create直接报错。cluster-announce-ip和cluster-announce-port这是让其他节点和客户端知道如何访问你的关键配置。在Docker的bridge网络里容器IP是固定的172.28.0.11如果漏了这三行还有个cluster-announce-bus-port节点会把自己的真实IP广播出去别人连不上集群状态会出现诡异问题。这里我踩过一个很深的坑一开始只配了cluster-announce-ip没配cluster-announce-bus-port。结果集群create成功但过了几分钟就有一堆节点标记为fail。后来查日志发现gossip通信走的是port 10000的bus端口容器内是16391但节点注册的是172.28.0.11:6391其他节点尝试用172.28.0.11:16391连bus由于我没显式声明它广播的是容器内默认的bus端口才导致互相探活失败。所以三个announce配置缺一不可。3. 启动集群固定IP、端口映射与节点身份的关系配置写好后六个容器可以一次性启动。我写成一条脚本方便以后在别的机器上复现for i in $(seq 1 6); do port$((6389 i)) # 6391~6396 ip172.28.0.1$i # 172.28.0.11 ~ 172.28.0.16 mkdir -p /data/redis-cluster/node-${i} docker run -d \ --name redis-node-${i} \ --net redis-cluster-net \ --ip ${ip} \ -p ${port}:${port} \ -v /data/redis-cluster/node-${i}:/data \ -v /data/redis-cluster/conf/node-${i}.conf:/etc/redis/redis.conf \ redis:7.0.13 \ redis-server /etc/redis/redis.conf done注意我做了三件事每个容器显式指定--ip保证节点地址固定端口映射-p 6391:6391方便宿主机直接调试虽然集群通信走容器网络但调试和客户端连接时能少一层麻烦挂载配置文件和data目录保证容器重启后配置不变、数据不丢。启动完成后先确认六个容器都是up状态然后各自检查日志有没有报错docker ps --format table {{.Names}}\t{{.Status}}\t{{.Ports}} docker logs redis-node-1 --tail 20这一步很关键不要急着直接建集群。我见过有人容器刚启动还没ready就急着执行集群创建命令结果报Node is not empty或者Waiting for the cluster to join。如果日志里出现Cluster state is not enabled之类的信息多半是配置文件没生效——检查挂载路径或者进容器里确认一下CONFIG GET cluster-enabled是不是yes。进容器确认的方式docker exec -it redis-node-1 redis-cli -p 6391 CONFIG GET cluster-enabled如果返回cluster-enabled: yes说明配置生效了。4. 创建集群meet、槽位分配、主从复制的完整逻辑这一步网上的教程大多只给一条命令但这条命令背后做了三件事理解透了你才能应付各种意外。docker exec -it redis-node-1 redis-cli \ --cluster create \ 172.28.0.11:6391 172.28.0.12:6392 172.28.0.13:6393 \ 172.28.0.14:6394 172.28.0.15:6395 172.28.0.16:6396 \ --cluster-replicas 1拆解一下前三台是主节点后三台是从节点--cluster-replicas 1表示每个主节点挂一个从节点命令内部自动执行了CLUSTER MEET——把六个节点互相认识自动分配16384个哈希槽slot给三个主节点分配方式是平均分主节点1分0~5460主节点2分5461~10922主节点3分10923~16383自动执行CLUSTER REPLICATE把后三台分别挂到前三台下面。执行过程中会问你Can I set the above configuration? (type yes to accept...)确认一下输入yes即可。看到[OK] All 16384 slots covered说明槽位全部分配成功。这里有几个判断标准建完集群立刻验证docker exec -it redis-node-1 redis-cli -p 6391 CLUSTER INFO docker exec -it redis-node-1 redis-cli -p 6391 CLUSTER NODESCLUSTER INFO重点关注cluster_state:ok和cluster_slots_assigned:16384。CLUSTER NODES会输出每个节点的ID、IP:port、角色、所属主节点、槽位范围。如果看到某个节点是fail或者有slaver节点没有关联主节点说明构建过程有问题。跑一个实际读写验证槽位路由这步必须做否则不算搭完# 写入一个key docker exec -it redis-node-1 redis-cli -p 6391 -c SET user:name zhangshan # 读取这个key docker exec -it redis-node-1 redis-cli -p 6391 -c GET user:name-c的意思是cluster模式客户端会自动跟随槽位跳到正确的节点。不加-c的话如果key不在当前节点会返回MOVED错误——这其实是正常行为别误会。上面整个过程就是完整的搭建流程。但在实际执行时我遇到过三种常见异常这里展开说一下排查链路。4.1 报错ERR Slot xxx is already busy出现这个信息说明节点上残留了槽位信息。最常见的原因是之前做过集群实验的容器数据目录没清干净新容器启动后读取了旧的nodes-*.conf认为自己已经有槽位。解决方式很粗暴但有效# 停掉所有redis-node容器 docker stop $(docker ps -q --filter nameredis-node) # 删除所有redis-node容器 docker rm $(docker ps -q -a --filter nameredis-node) # 彻底删除数据目录里的旧文件 rm -rf /data/redis-cluster/node-* # 重新启动 # 重新执行第3章的启动脚本即可注意节点ID是随机生成的数据目录一旦丢失节点ID就变了。生产环境千万别随意删数据目录实验环境无所谓放手删。4.2 集群创建卡在Waiting for the cluster to join这个坑我花了整整半天才定位。核心原因是节点之间gossip通信失败。排查链路如下第一步确认六个容器IP是否固定且可达docker exec -it redis-node-1 ping 172.28.0.12如果不通检查网络段是不是建错了第二步确认bus端口通信正常gossip用的是port 10000的端口6391对163916392对16392以此类推如果cluster-announce-bus-port没配节点会广播默认计算值容器里是通的但换到其他网络环境后就不通。建议无论如何都显式声明第三步看容器日志里有没有Possible IP address resolution error之类的告警如果有大概率是announce配置缺失或写错了。这件的排查思路可以总结为一句话先用CLUSTER NODES看节点眼里其他节点的地址再对着实际网络逐跳ping确定是广播地址错了还是路由不通。4.3 节点被标记为fail的最新原因跨版本兼容如果混用了不同的Redis小版本比如一个节点是6.2另一个是7.0节点间可能因为协议不兼容导致互相判fail。我在测试环境就遇到过6.2和7.0混搭集群能建立但主节点频繁被标记为fail换回统一版本后问题消失。所以搭集群之前先统一镜像版本这一步能帮你省掉大量排错时间。5. 集群模式下的常规运维操作扩容、缩容、槽位迁移集群建好只是开始后面扩容和缩容才是真正考验理解深度的地方。我先讲扩容因为这是生产里最可能用到的操作。5.1 水平扩容新增一主一从假设我要加第七、第八个节点。先启动两个新容器IP分别是172.28.0.17和172.28.0.18端口6397、6398配置文件照前面模板改好启动后不要直接执行--cluster create而是用add-node把它们加入现有集群# 加入主节点 docker exec -it redis-node-1 redis-cli \ --cluster add-node \ 172.28.0.17:6397 \ 172.28.0.11:6391 # 加入从节点并指定跟随某个主节点 docker exec -it redis-node-1 redis-cli \ --cluster add-node \ 172.28.0.18:6398 \ 172.28.0.11:6391 \ --cluster-slave \ --cluster-master-id 主节点IDadd-node的第一个参数是新节点第二个参数是集群里任意一个已知节点。新节点加入后默认不带任何槽位整个集群的16384个槽位还是原来的三台主节点平分。这时候用CLUSTER NODES能看到新节点处于handshake状态过一会变成master但槽位为空。接下来要重新分配槽位让新节点也承担一部分数据。用reshard命令docker exec -it redis-node-1 redis-cli --cluster reshard 172.28.0.11:6391这是个交互式命令会问你How many slots do you want to move (from 1 to 16384)?输入你想迁移的槽位数量比如3500What is the receiving node ID?输入新节点的IDSource node IDs输入all从所有现有主节点均摊或者指定某个主节点ID。确认后槽位迁移会自动执行期间涉及到的key会通过MIGRATE命令在各个节点间搬运业务读写不受影响但部分key的访问RT会临时变高。槽位迁移完成后再用CLUSTER INFO确认cluster_slots_assigned:16384且cluster_state:ok。5.2 缩容手动迁移槽位再下线节点缩容不能直接docker stop否则那个节点负责的槽位会无人管理整个集群进入fail状态。正确步骤是先把要下线节点上的所有槽位迁移到其他节点再让它变成空节点最后用forget节点。第一步用reshard反向操作把目标节点的槽位全部迁移走。如果目标是节点ID为xxxx的主节点执行docker exec -it redis-node-1 redis-cli --cluster reshard 172.28.0.11:6391 \ --cluster-from 要下线的主节点ID \ --cluster-to 接收槽位的主节点ID \ --cluster-slots 4096 \ --cluster-yes注意--cluster-slots填的是该节点现有的全部槽位数。如果你不知道具体数字可以用docker exec -it redis-node-1 redis-cli -p 6391 CLUSTER SLOTS查看每个主节点负责的槽位段。第二步槽位迁移干净后确认该节点上的槽位数为0docker exec -it redis-node-1 redis-cli -p 6391 CLUSTER COUNTKEYSINSLOT 0如果节点上没有槽位它还有可能作为某些从节点的主节点不会只要它是主节点且槽位为空就可以执行forget。第三步在所有其他节点上都执行CLUSTER FORGET把下线节点从集群拓扑中移除docker exec -it redis-node-1 redis-cli -p 6391 CLUSTER FORGET 下线节点ID docker exec -it redis-node-2 redis-cli -p 6392 CLUSTER FORGET 下线节点ID然后才能安全地docker stop和docker rm。之所以要逐个节点forget是因为节点ID是持久化的如果只在一个节点上forget其他节点还会保留它的信息重启后又会被gossip带回来形成僵尸节点。5.3 从节点提升与主节点故障转移非哨兵模式下Redis Cluster自己带故障转移能力。当某个主节点挂了网络不通或进程退出它的从节点会发起选举如果大多数主节点投票通过从节点就提升为新的主节点。这个过程不是立即的跟cluster-node-timeout有关默认15秒实测一般在20~30秒内完成。验证方式很简单# 手动kill掉主节点1 docker stop redis-node-1 # 等30秒后查看集群状态 docker exec -it redis-node-4 redis-cli -p 6394 CLUSTER NODES如果发现原来node-1对应的从节点状态从slave变成了master说明故障转移成功。但注意原主节点恢复后它不会自动变成从节点而是以master身份重新加入集群但不带槽位。这时候集群有两个master共享同一组历史槽位一个持有实际槽位另一个空槽如果不处理下次故障可能出现脑裂风险。正确的做法是手动把恢复的原主节点降级为从节点docker exec -it redis-node-4 redis-cli -p 6394 CLUSTER REPLICATE 新主节点ID这不仅是个运维动作也是一个常见的坑。6. 我踩过的几个配置坑每个都值得记下来这一节不按顺序写按最容易中招排序都是我反复验证过的坑。6.1 只映射端口不固定IP——重启后集群成员大乱这是Docker搭Redis集群最常见的翻车点。如果你用默认bridge网络每次docker restart之后容器IP会重新分配节点ID虽然不变但IP变了其他节点缓存的是旧IPgossip彻底失效。表现出来就是CLUSTER NODES里各种disconnected严重的直接fail。解决方式有两个一是建自定义网络后显式指定--ip本文方案二是用docker compose配置networks下的ipv4_address原理一样。6.2 配置文件挂载到容器后竟然没生效我一开始图省事把所有配置直接写进docker run命令的--appendonly yes、--cluster-enabled yes这种flag里结果CONFIG GET cluster-enabled始终是no。后来看了官方镜像的entrypoint逻辑才明白redis镜像的entrypoint把redis-server命令后的参数当作redis配置项处理但Dockerfile里可能设置了默认配置flag和挂载配置混用时以最后一个为准。最稳妥的方式是只挂载一个完整的conf文件不用命令行flag。另外挂载时要保证配置文件末尾有换行符别问我为什么知道——有一次配置文件最后一行少了换行容器启动后redis-server直接报Bad directive or wrong number of arguments排查了半天才发现是这种低级问题。6.3 不同容器间时间不同步这是个很容易被忽略的隐性坑。Redis Cluster节点间通信虽然不强制要求时钟完全一致但故障选举和心跳超时判断会受时间偏差影响。如果时间差太大可能出现主节点活着却被从节点误判为超时的现象。在Docker环境里如果宿主机时间同步正常容器默认继承宿主机时间一般没问题但如果用的是某云的容器服务容器时间可能被虚拟化层干扰建议在宿主机上装好chrony或ntp再在docker-compose里加/etc/localtime:/etc/localtime:ro的挂载。6.4 客户端连接集群必须用集群模式驱动项目里连集群时不要用普通的Jedis直连单节点否则会频繁碰到MOVED或ASK重定向。正确做法是使用集群模式的客户端比如Java的JedisCluster、LettucePython的redis-py-clusterGo的go-redis的ClusterClient。我的实际建议是小项目、内部工具用JedisCluster最省事自动处理槽位路由和max redirects高并发、需要连接池和响应时间控制的用Lettuce性能更好但对超时配置要仔细调连接字符串里配至少两个节点的地址避免一个节点挂了客户端完全无法发现集群拓扑。7. 生产环境使用Docker版Redis集群的几点提醒测试环境搭完之后如果要把这套方案搬到生产有几个地方和测试环境差别很大提前说清楚。第一网络模式。如果是多台物理机或虚拟机组成集群每台机上的容器网络要打通。我的做法是给每台机器上的Redis容器都用host网络模式直接占用宿主机端口然后通过Docker的restart: always策略确保容器跟随机器启动。其实也就绕过了容器网络让Redis behave like一个普通的常驻进程。但一旦用host模式cluster-announce-ip必须配成每台机器的物理网卡IP不能是容器IP。第二防火墙和安全组。除了业务端口记得放行bus端口port 10000。很多同事在云上只开了6379结果集群建不起来日志刷一堆connect timed out。至少要放行TCP端口范围业务端口段 对应的bus端口段。绑定的IP也要限制在指定的内网网段不要暴露到公网。第三内存与持久化。Cluster模式本身有主从复制但复制不能替代持久化。建议开启appendonly yes并把AOF文件放到持久化卷。实测在生产里appendfsync建议用everysec兼顾性能和数据安全。no的话性能最好但可能丢1秒以上数据always太慢吃不住高写入。至于RDB作为兜底可以开但别太频繁——两个机制都开启时重启恢复会比较慢。第四maxmemory要谨慎设置。如果设置了maxmemory且策略是allkeys-lru之类的淘汰策略注意槽位迁移期间key数量增多可能触发淘汰导致另一种数据丢失的观感。建议先估算每个节点的数据量设置合理的maxmemory并监控好evicted_keys指标。第五健康检查。容器层面的HEALTHCHECK很有用。我用的是HEALTHCHECK CMD redis-cli -p 6391 -c CLUSTER INFO | grep cluster_state:ok || exit 1这样编排平台就能感知节点是否处于健康的集群状态而不只是进程还活着。8. 从0到1的完整复现速查表为了方便你直接照着操作我把整篇文章的可用命令压缩成一个速查表。假设你要在单机搭一个6节点的Redis Cluster3主3从照着跑就行。阶段命令/操作预期结果建网络docker network create redis-cluster-net --subnet 172.28.0.0/16网络创建成功建目录for i in $(seq 1 6); do mkdir -p /data/redis-cluster/node-${i}; done6个目录出现写配置按第2章模板写6份conf改端口和IP各conf文件就位启动容器按第3章脚本执行6个容器up验证配置docker exec redis-node-1 redis-cli -p 6391 CONFIG GET cluster-enabled返回yes建集群按第4章--cluster create命令执行输入yes16384 slots covered验证读写docker exec redis-node-1 redis-cli -p 6391 -c SET foo barOK验证拓扑CLUSTER NODES3 master 3 slave无fail测试故障转移docker stop redis-node-1等30秒后查CLUSTER NODES原从节点提升为master整个流程如果顺利大概15分钟可以搭完。我建议你在自己电脑上至少完整走三遍——第一遍照着抄第二遍不看文章自己推第三遍故意制造几个故障比如把某个配置删掉再排错。三遍下来你才算真正会搭Redis集群了而不是照着别人教程敲了一遍命令。顺带说一句如果你以后要搭更复杂的多机房集群核心还是理解槽位、gossip、主从投票这三个底层机制Docker只是承载方式。把这套容器化的流程玩熟练了再换裸机部署、再换K8s部署底层逻辑都是一样的变的只是网络和进程管理的外壳。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑