Docker部署Redis 7.0.7集群:从原理到实操
发布时间:2026/10/14 13:42:44 锦皓数字建站

手头要搭 Redis 7.0.7 的集群又不想在物理机上一台一台装 Redis用 Docker 把 6 个节点一组拉起来自然是最顺手的方案。这篇就把我自己的实操过程完整记录下来从集群原理到容器编排、从配置选型到故障排查都过一遍帮还没动手的你把坑提前踩了。1. 这个教程要解决的问题与选型思路1.1 什么时候需要 Redis ClusterRedis 单机部署很简单但一旦业务体量上来单节点的瓶颈就很明显了。常见情况是内存涨到几十 GB 后RDB 快照和 AOF 重写导致主线程阻塞严重或者 QPS 到了十万级别单线程的事件循环开始吃紧又或者纯粹的容灾需求某一台机器挂了不能接受。此时引入 Redis Cluster 是要把三件事一起解决数据分片、高可用、水平扩展。集群把 key 分散到多个主节点上每个主节点再配一个或多个从节点做实时复制。主节点挂掉后从节点会自动提升接管客户端基本无感知。不过要注意Cluster 不是为了解决所有问题的银弹。它不支持多 key 的操作跨节点执行事务、Lua 脚本和多键命令都要求所有 key 落在同一个槽上。如果你的业务大量使用跨 key 的 mget、multi-key 操作使用集群前必须先把业务逻辑梳理一遍。1.2 为什么要用 Docker 搭建很多人问为什么不用三个或多个真实节点因为很多开发环境根本没有三台机器而且一台机器上跑多个单机 Redis 实例本来就很别扭。Docker 的好处是每个容器有自己的 IP 和网络命名空间Redis Cluster 要求各节点通过 IP 通信的特性可以被完整模拟跟真实多机部署几乎一致。用 Docker 还方便做一次性实验。集群拉起来测试完一条命令全部销毁不留痕迹。对学习 Cluster 原理、测试 failover 流程、验证运维脚本来说这比开三台云服务器或者维护三套本地虚拟机都省心得多。另一个实际原因是 Redis 7.x 的二进制包和编译器环境未必适配你本机的操作系统而官方镜像已经帮你编译好并做了大量优化直接拉取比源码安装省时间。1.3 版本选择为什么要用 7.0.77.0.7 是 7.0 系列中比较稳定的补丁版本。Redis 7.0 相比 6.x 有几个对集群运维影响较大的变化AOF 支持多级合并的机制重写效率更高引入了 Functions 以更系统化地替代部分 Lua 脚本场景对集群节点通信和槽位迁移的错误处理做了一些改良。生产环境首次上集群选一个已经沉淀过的补丁版本比追新版本更稳妥。这个镜像在 Docker Hub 上有标准 tags 对应redis:7.0.7可以直接拉取。默认配置就是专为容器运行优化的这点比自己在物理机上手工编译再配置要省事不少。2. Redis Cluster 集群原理快速复盘2.1 槽位分配与数据路由机制Redis Cluster 将整个 keyspace 划分为 16384 个哈希槽。每个 key 通过CRC16(key) % 16384计算出一个槽编号再由该槽所属的节点负责读写。这个槽位数量是精心设计的16384 2 的 14 次方CRC16 是一个 16 位校验算法取低 14 位正好覆盖且这个数字在不超过 1000 节点的集群规模下能满足均匀分配的需求。官方文档明确表示槽位总数不宜超过 16384因为节点间传递槽位配置信息时有带宽和压缩开销。当你把 3 个主节点组成集群后这些槽会被尽量均匀地划分到三个节点上。客户端要求访问某个 key 时随便连接集群中任何一个节点如果 key 对应的槽不在当前节点上节点会返回一个MOVED错误内容包含目标节点的 IP 和端口。智能客户端会缓存这个路由信息所以第二次访问就能直接命中正确节点。另外还有一个ASK重定向发生在槽位迁移过程中。如果你正在执行reshard部分 key 可能还在源节点部分已经搬到目标节点。这种情况下源节点会返回ASK要求客户端先发一个ASKING命令再进行访问避免在迁移过程中读到不一致的数据。很多人只用过MOVED遇到ASK往往一头雾水这里提前说明。2.2 主从复制与故障转移每个主节点至少需要一个从节点。从节点通过全量同步加增量复制的方式跟进主节点的数据。当主节点发生故障时从节点会自动发起选举多个从节点中谁能赢得过半主节点的投票谁就升级为新的主节点。这个机制跟 Raft 的精髓类似每个从节点等待一个小随机时间然后要求其他节点投票。得票超过半数且大于或等于一个票数阈值时它就能成功接管。正因如此如果 3 个主节点的集群中有一个主节点及其从节点同时全部故障集群就会进入不可用状态因为剩余的两个主节点无法构成大多数。这里要强调一个实践中的点3 主 3 从、6 个节点的组合是最常用形态。这既满足每个主有一个备份又能在单节点故障时保持多数可用。在 Docker 里为了方便测试可以只做 3 主 3 从也就是本教程默认的形式。2.3 集群总线与节点间通信Cluster 模式下的每个 Redis 节点会额外开启一个集群总线端口默认是数据端口加 10000。比如数据端口 6379总线端口就是 16379。节点之间通过这个总线端口进行心跳检测、配置交换、故障上报。心跳包是二进制的而不是普通的 RESP 协议这样能显著降低带宽占用。在 Docker 环境里一个最常见的坑就是忘了开放总线端口。你把 6379 映射出来了但 16379 没映射结果集群一直报Waiting for the cluster to join根本原因就是节点间在总线上无法通信。后面排查章节还会专门讲。2.4 Docker 环境下的网络模型选择Docker 下跑 Redis Cluster 有两种主流网络方案host网络和自定义bridge网络。用host网络最省心因为容器直接用宿主机的网络栈和服务端口节点间的总线端口自然互通不需要额外做端口映射。缺点也很明显容器之间没有网络隔离所有服务都抢占宿主机端口集群节点超过六七个时端口管理会变得混乱。自定义bridge网络是我个人最推荐的做法创建一个 Docker 网络每个容器静态分配一个固定 IP端口在容器内部保持默认不需要映射到宿主机。这样多个容器之间用内网 IP 直接互通与真实物理部署的拓扑几乎一致排查问题也更直观。唯一要注意的是如果宿主机上有安全组或防火墙需要允许内网网段之间任意端口通信。3. 部署前准备工作3.1 Docker 环境检查与镜像准备先确认你的 Docker 版本能正常使用。我在部署前习惯跑一遍docker --version docker compose version docker info --format {{.ServerVersion}}如果docker info能正常返回Server Version说明 Docker daemon 运行状态没问题。这里提醒一句如果你的 Docker 环境是桌面版内存分配至少要给 4GB内存太小6 个 Redis 容器一起启动时容易出现动态分配失败或者 OOM。确认之后直接拉取镜像docker pull redis:7.0.7 docker images | grep redis如果拉取速度比较慢可以配置 registry mirror但不是必要的。拉完确认一下镜像大小一般在 150MB 左右。如果你本地之前有别的版本建议专门给 7.0.7 打好 tag避免后面启动命令写错镜像名。3.2 端口规划与卷目录规划本教程采用如下端口规划6 个节点在同一宿主机上但彼此通过自定义网络的固定 IP 通信节点容器名数据端口总线端口Redis-master-1redis-cluster-7001700117001Redis-master-2redis-cluster-7002700217002Redis-master-3redis-cluster-7003700317003Redis-slave-1redis-cluster-7004700417004Redis-slave-2redis-cluster-7005700517005Redis-slave-3redis-cluster-7006700617006卷目录规划如下每个节点对应一个独立目录用来挂载配置文件和数据持久化文件mkdir -p /data/redis-cluster/{7001,7002,7003,7004,7005,7006}/conf mkdir -p /data/redis-cluster/{7001,7002,7003,7004,7005,7006}/data为什么要单独为每个节点建目录因为容器是无状态的容器删除后数据会全部丢失。将数据目录挂载到宿主机可以保证容器重建后数据仍然存在。配置文件也放在宿主目录方便你调整参数而无需进入容器修改。3.3 节点配置文件模板每个节点都需要一个 Redis 配置文件。以 7001 节点为例我在/data/redis-cluster/7001/conf/redis.conf中写入以下内容port 7001 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes appendfilename appendonly-7001.aof appendfsync everysec save 900 1 save 300 10 save 60 10000 protected-mode no dir /data关键参数解释一下cluster-enabled yes开启集群模式。cluster-node-timeout 5000节点超时时间单位毫秒。超过 5 秒没有收到心跳则认为节点故障这个值太短会造成频繁的主从切换太长则会让故障时间拉长。appendonly yes和appendfsync everysec开启 AOF 持久化每秒刷新一次。这样可以保证在故障时最多丢失 1 秒的数据同时不至于因为每次写操作都 fsync 而过量消耗磁盘 IO。cluster-config-file每个节点自己维护的集群状态文件启动时创建运行中自动更新。这里每个节点用独立文件名避免多个容器共享相同文件导致冲突。6 个节点的配置文件除了port、appendfilename和目录名不同其他完全一样。为了节省时间你可以循环生成for port in 7001 7002 7003 7004 7005 7006; do cat /data/redis-cluster/$port/conf/redis.conf EOF port $port cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes appendfilename appendonly-$port.aof appendfsync everysec save 900 1 save 300 10 save 60 10000 protected-mode no dir /data EOF done生成完以后逐个检查一遍。特别是port必须和目录名、容器规划完全一致不然后面容易把自己绕晕。4. 集群部署实操全流程4.1 创建专用网络并启动节点容器先创建一个自定义 bridge 网络。我在这里故意把网络段的第三位设为 20避免和宿主机网段混淆也方便在 Redis 节点信息里一眼认出哪个是容器 IP。docker network create --subnet172.20.0.0/16 redis-cluster-net然后规划 6 个容器的固定 IP。我把主节点分配为容器名固定 IPredis-cluster-7001172.20.0.11redis-cluster-7002172.20.0.12redis-cluster-7003172.20.0.13redis-cluster-7004172.20.0.14redis-cluster-7005172.20.0.15redis-cluster-7006172.20.0.16用循环启动会高效很多。以 7001 节点为例单独展开的启动命令如下docker run -d \ --name redis-cluster-7001 \ --network redis-cluster-net \ --ip 172.20.0.11 \ -p 7001:7001 \ -p 17001:17001 \ -v /data/redis-cluster/7001/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis-cluster/7001/data:/data \ redis:7.0.7 \ redis-server /etc/redis/redis.conf解释几个点--ip 172.20.0.11强制指定容器 IP。注意一定要在创建网络时指定了--subnet否则 Docker 无法给容器分配你期望的固定 IP。-p 7001:7001 -p 17001:17001把数据端口和总线端口都映射到宿主机这样既便于从宿主机连接也方便你在外部抓包观察节点通信。-v /data/redis-cluster/7001/conf/redis.conf:/etc/redis/redis.conf配置文件只读挂载不希望容器运行时随意改写你的模板。-v /data/redis-cluster/7001/data:/data数据目录挂载容器里 Redis 的工作目录是/data所有持久化文件都写到这个位置。把 CDN 替换掉再循环跑一遍for port in 7001 7002 7003 7004 7005 7006; do ip172.20.0.$((10 ${port: -1} 1)) docker run -d \ --name redis-cluster-${port} \ --network redis-cluster-net \ --ip ${ip} \ -p ${port}:${port} \ -p $((port 10000)):$((port 10000)) \ -v /data/redis-cluster/${port}/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis-cluster/${port}/data:/data \ redis:7.0.7 \ redis-server /etc/redis/redis.conf done启动完成后检查一下容器数量和日志docker ps --filter nameredis-cluster --format table {{.Names}}\t{{.Status}}\t{{.Ports}} docker logs redis-cluster-7001 --tail 20如果日志正常你应该能看到Ready to accept connections。如果出现FATAL CONFIG FILE ERROR之类的报错多半是配置文件里的dir /data在容器内不存在检查挂载是否成功。4.2 构建集群与验证分片6 个容器全部启动后用一个命令即可完成集群构建。进入其中一个主节点容器执行docker exec -it redis-cluster-7001 \ redis-cli --cluster create \ 172.20.0.11:7001 172.20.0.12:7002 172.20.0.13:7003 \ 172.20.0.14:7004 172.20.0.15:7005 172.20.0.16:7006 \ --cluster-replicas 1这个命令的含义是让 7001 节点作为发起者把这 6 个节点组建成一个集群每个主节点配备 1 个从节点。执行过程中 Redis CLI 会为你规划主从分布直接使用默认分配即可。当你看到如下提示时输入yes确认Can I set the above configuration? (type yes to accept):完成之后你会看到一段报告包括槽位分配结果和各节点的主从关系。此时集群已经可用了。验证集群状态是接下来最重要的一步。我建议做三件事docker exec -it redis-cluster-7001 redis-cli -c -h 172.20.0.11 -p 7001 cluster info docker exec -it redis-cluster-7001 redis-cli -c -h 172.20.0.11 -p 7001 cluster nodes docker exec -it redis-cluster-7001 redis-cli -c -h 172.20.0.11 -p 7001 cluster slotscluster info输出中重点关注cluster_state:ok集群正常。cluster_slots_assigned:1638416384 个槽位全部完成分配。cluster_known_nodes:6节点总数为 6。cluster nodes输出中每个节点的行首是节点 ID第一列显示myself的就是当前连接的节点。注意观察每行的角色标记master还是slave以及双方的master指向关系。cluster slots则能更直观看到每个主节点负责的槽位范围。4.3 使用 redis-benchmark 验证写入分布静态查看槽位分配还不够直观建议用redis-benchmark跑一批写入再确认 key 确实均匀分布在多个主节点上。在 7001 节点容器里执行docker exec -it redis-cluster-7001 \ redis-benchmark -h 172.20.0.11 -p 7001 \ -t set -n 10000 -r 100000 \ -c 50 -P 10这里参数含义分别是-t set只测 SET 命令-n 10000执行 1 万个请求-r 100000预生成 10 万个随机 key-c 50是 50 个并发连接-P 10是每个连接 pipeline 10 条请求。跑完之后分别在三个主节点上统计当前 key 数量docker exec -it redis-cluster-7001 redis-cli -h 172.20.0.11 -p 7001 dbsize docker exec -it redis-cluster-7002 redis-cli -h 172.20.0.12 -p 7002 dbsize docker exec -it redis-cluster-7003 redis-cli -h 172.20.0.13 -p 7003 dbsize三个值加起来应该等于 1 万条左右-n 10000。如果三个值分布相对均衡说明 CRC16 的散列效果没问题。如果差距悬殊多半是因为生成的测试 key 模式过于集中可以调整-r范围重测。这一步能帮你提前发现写入热点问题避免上线后才发现某个节点容量吃紧。4.4 docker-compose 方式管理集群手工docker run的方式适合学习但日常开发中我更推荐用docker-compose.yaml来统一管理尤其是集群需要频繁重建时。我通常这样写services: redis-cluster-7001: image: redis:7.0.7 container_name: redis-cluster-7001 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.11 ports: - 7001:7001 - 17001:17001 volumes: - /data/redis-cluster/7001/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7001/data:/data redis-cluster-7002: image: redis:7.0.7 container_name: redis-cluster-7002 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.12 ports: - 7002:7002 - 17002:17002 volumes: - /data/redis-cluster/7002/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7002/data:/data redis-cluster-7003: image: redis:7.0.7 container_name: redis-cluster-7003 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.13 ports: - 7003:7003 - 17003:17003 volumes: - /data/redis-cluster/7003/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7003/data:/data redis-cluster-7004: image: redis:7.0.7 container_name: redis-cluster-7004 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.14 ports: - 7004:7004 - 17004:17004 volumes: - /data/redis-cluster/7004/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7004/data:/data redis-cluster-7005: image: redis:7.0.7 container_name: redis-cluster-7005 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.15 ports: - 7005:7005 - 17005:17005 volumes: - /data/redis-cluster/7005/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7005/data:/data redis-cluster-7006: image: redis:7.0.7 container_name: redis-cluster-7006 command: redis-server /etc/redis/redis.conf networks: redis-cluster-net: ipv4_address: 172.20.0.16 ports: - 7006:7006 - 17006:17006 volumes: - /data/redis-cluster/7006/conf/redis.conf:/etc/redis/redis.conf - /data/redis-cluster/7006/data:/data networks: redis-cluster-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16如果有编排工具还可以用depends_on和healthcheck做启动顺序控制。但没有也不影响因为redis-cli --cluster create本身会等待节点可用。使用 compose 部署的好处是一条docker compose up -d全部拉起一条docker compose down全部销毁目录里的配置和维护逻辑一目了然。5. 常见问题与排查经验实录5.1 节点一直握手失败症状执行--cluster create后卡在Waiting for the cluster to join或者节点日志里反复出现Unable to connect。排查思路先看节点日志执行docker logs redis-cluster-7001 | grep -i error看看是否有UNKNOWN错误或握手失败信息。检查 6 个容器是不是都在同一个 Docker 网络上用docker network inspect redis-cluster-net查看容器 IP确认它们都在该网络的容器列表中。手动测试两个节点之间的连通性docker exec -it redis-cluster-7001 redis-cli -h 172.20.0.12 -p 7002 ping。能通说明网络没问题问题出在集群握手逻辑不能通则大概率是网络隔离或防火墙问题。确认每个节点的cluster-enabled yes已生效。用redis-cli -h 172.20.0.11 -p 7001 info cluster查看cluster_enabled:1。最常见的实测原因是端口映射错了或者根本没有把总线端口数据端口 10000映射出来。Docker 容器内节点之间通过自定义网络通信时实际上不需要宿主机端口映射但如果你用host网络模式端口必须占全。5.2 集群状态 FAIL 但节点都存活的诡异现象症状cluster_state:fail但 6 个容器都活着cluster nodes也能看到所有节点就是状态不健康。原因一般是以下两个某个节点的cluster-node-timeout设置过小再加上宿主机 CPU 负载高或 IO 阻塞心跳超时被判定为故障导致集群进入 FAIL 状态。解决办法是调大cluster-node-timeout比如从 5000 调整到 15000 毫秒。时钟漂移。Docker 容器的时钟是从宿主机继承的假如宿主机长时间运行且 NTP 同步有问题各节点的时间戳差距过大心跳包会被忽略最终节点间持久性失联。解决办法是确保宿主机开启了时间同步。实测中出现cluster_state:fail时可以登录任意节点执行cluster set-config-epoch手动干预不过这属于高级操作生产环境不建议用最好是直接把问题节点的数据清掉重启容器让它在集群里重新同步。5.3 容器重启后 IP 变了导致集群散架这个问题在 Docker 部署中非常高频。默认 bridge 网络的 IP 是动态分配的容器重启时可能被分配到不同的 IP。Redis Cluster 的集群状态文件nodes.conf会记录每个节点的 IP一旦节点重启后 IP 改变原 IP 对应的端口依旧开着但新节点可能已经无法作为原 ID 加入集群。解决办法就一条铁律创建一个自定义网络时显式指定--subnet然后在每次docker run时显式指定--ip。有些朋友图省事不指定固定 IP短期没问题一旦容器被重建或者 Docker daemon 重启IP 漂移就会让你崩溃。如果 IP 已经变了最干净的恢复方式是把所有节点的nodes.conf和dump.rdb清掉然后用cluster create重新建集群。注意在测试环境这样处理没问题但生产环境一定要有备份和切换预案。5.4 故障转移实测与手动触发建好集群后我强烈建议做一次故障转移实验。实际操作很简单停止一个主节点容器比如docker stop redis-cluster-7001等几秒后观察它的从节点是否自动提升为新主节点。docker exec -it redis-cluster-7002 redis-cli -h 172.20.0.12 -p 7002 cluster nodes | grep 7001正常情况下你会在 10 到 15 秒内看到一个从节点的角色变为master原来的 7001 主节点会被标记为fail或处于disconnected状态。然后重启被停止的容器你会发现它自动作为从节点重新加入集群继续同步新主节点的数据。如果不想真的 kill 容器也可以用cluster failover命令手动触发优雅切换docker exec -it redis-cluster-7004 redis-cli -h 172.20.0.14 -p 7004 cluster failover执行后要观察一下原主节点和从节点的角色交换情况。如果成功7004 会成为新的主节点原来的 7001 变成它的从属。用这种方式测试集群切换能力不会产生数据丢失非常适合真实演练。我每次搭完集群都会跑一遍failover确认自动提升逻辑没问题后再把集群交付给业务方。实操中的几点最终体会如果把整个流程再压缩成几条经验我最想说的是Docker 跑 Redis Cluster 的核心痛点不在 Redis而在网络规划和状态文件管理。固定 IP、全端口开放、独立数据卷、统一的配置文件模板这四点做好了集群基本不会出大问题。我在实际使用中还有一个习惯每次重建集群前会把所有节点数据目录里的nodes.conf删掉而不是只删容器。因为nodes.conf里保存的是旧 IP 和旧节点 ID容器重建时它还会试图用旧身份接入集群造成各种奇怪的抖动。删除后重新创建容器Redis 会在启动时自动生成全新的集群身份干净利落。最后提醒一下这套 3 主 3 从的 Docker 部署方案非常适用于开发、测试和个人学习环境。如果要接生产流量一定要加上监控告警、数据备份策略和客户端路由重试机制集群本身不是全部围绕集群的运维体系才是让你睡得着觉的关键。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。