Docker 搭建 Redis Cluster 实战:从分片原理到高可用排坑

📅 发布时间:2026/10/19 12:21:41
Docker 搭建 Redis Cluster 实战:从分片原理到高可用排坑
开局先坦白一下把这套环境跑通之前我一直觉得 Redis 集群是个“要么不用一用就得先折腾好几台机器”的复杂活。以前团队里做高可用清一色主从加哨兵Redis Cluster 在我印象里就是个维护成本偏高的存在。直到某次压测发现单实例内存先撑不住了才真正意识到哨兵只能保证“不宕机”解决不了“装不下、扛不住”于是趁着周末用 Docker 在本地从零搭了一套非哨兵模式的 Redis 集群也就是 Native Cluster 模式。这篇文章适合还分不清“集群”和“哨兵”的人更适合准备上集群、想先在自己机器上完整验证一遍的同学。整个过程我都是亲测过的遇到的问题也都写在后面直接照着来就行。1. 动手之前先想清楚Redis 集群和哨兵到底差在哪1.1 别再“一谈高可用就上哨兵”哨兵模式核心就一句话挂了能切换。主节点负责写从节点负责同步复制哨兵进程负责监控并在主节点不可用时把某个从节点提升成新的主节点。但数据本质上还是同一份所有人的读写都落在一个逻辑实例上单节点的内存上限就是整个缓存体系的上限。数据量涨到几十 GB或者单实例 CPU 先跑满哨兵帮不上任何忙。Cluster 模式解决的是另一个维度的难题数据分片。整个数据集被切成 16384 个哈希槽位hash slot每个 key 经过 CRC16 运算后再对 16384 取模结果决定它属于哪个槽位而每个主节点负责其中一段槽位。写入请求天然被分散到不同节点容量可以横向加机器来扩展。所以一句话对比就是哨兵负责“挂了好切换”Cluster 负责“装得下也扛得住”。Cluster 同样可以给每个主节点配从节点主节点挂了也有副本顶上两者并不冲突。只是很多教程默认把“主从哨兵”当作唯一高可用方案导致大家下意识忽略了 Cluster 的扩展价值。如果业务量存在明确的增长预期与其以后迁移不如一开始就按集群模式设计。1.2 为什么用 Docker 而不是裸机六个 Redis 节点就算全放一台物理机上手动部署也得改端口、改配置、逐个启动排查问题还得手工清进程。用 Docker 之后容器创建秒级完成配置文件挂载成 volume任何节点想重置就删容器几分钟内整个环境回到干净状态这种“可重复性”对学习新技术特别重要。Docker 还有一个隐藏优势网络模式可以灵活切换。我自己测试时先用 host 网络把流程跑通想模拟更接近生产的部署再切到自定义 bridge 网络并配上固定 IP。在裸机或虚拟机上要做到这种“想换网络环境就换网络环境”的成本高得多。当然 Docker 也不是没代价容器网络的透明度和宿主机不一样Redis Cluster 的集群总线端口、容器 IP 漂移这类问题不亲自踩一遍很难有直观感受。这些坑后面会全部展开。2. 环境准备与踩坑预判2.1 镜像版本怎么选直接用官方镜像 redis:7.2-alpine 就够了。为什么不建议老版本虽然 redis-cli 的 --cluster 子命令很早就有了但老版本在创建集群、处理密码、修复槽位这些场景下明显难用。5.x 之前的创建流程甚至要先装 redis-trib.rb 脚本依赖 Ruby 环境配置麻烦不说还容易版本不兼容。7.x 的 redis-cli 一个人就能完成 create、check、reshard、rebalance 整套操作少一个依赖就少一个坑。alpine 镜像小拉取快、占用少适合测试环境。但如果你习惯进容器里敲排查命令要注意它默认的 busybox 工具集和标准发行版略有差异必要时用 apk add 补装比如 procps。真正定位问题主要还是靠日志和 redis-cli这一点和宿主机部署没有区别。2.2 网络模式这一步不提前决定后面全是泪这是整个搭建过程里最容易翻车的点值得单独说透。Linux 环境下最简单的是 --network host。容器直接复用宿主机网络栈不需要做端口映射Redis 的客户端端口和集群总线端口全部自然暴露在宿主机上。好处是配置少、节点间通信顺畅对新手最友好缺点是宿主机端口被其他服务占用就冲突而且 Docker Desktop for Mac/Windows 对 host 网络的支持有限想在自己的笔记本上照着跑得先确认平台支持情况。如果走默认 bridge 网络每个容器有自己的内网 IP宿主机通过 -p 7001:7001 这类映射访问客户端端口。但这里藏着两个非常隐蔽的坑。第一个坑Redis Cluster 节点间互相通信不只用 7001 这类端口还会用“客户端端口 10000”的集群总线端口也就是 17001。这是专门用于 Gossip 协议、故障发现、投票握手的通道。只映射 7001 不映射 17001节点之间可能客户端端口能连上但总线握手会一直失败现象就是集群状态迟迟不 ok。第二个坑bridge 网络下容器 IP 在重建后可能变化而集群内部保存的节点信息还是旧 IP节点会一直显示 handshake所有请求都异常。我亲测后的建议是本地验证从 host 网络起步先理解端口和总线关系要模拟生产再换成自定义 bridge 网络搭配固定 IP。两种模式都跑一遍比只看文档有用得多。2.3 端口与目录规划我规划的 6 个节点一共是 3 主 3 从客户端端口用 7001 到 7006对应总线端口就是 17001 到 17006。目录结构按端口分开/data/redis-cluster/redis-7001/redis.conf /data/redis-cluster/redis-7002/redis.conf /data/redis-cluster/redis-7003/redis.conf /data/redis-cluster/redis-7004/redis.conf /data/redis-cluster/redis-7005/redis.conf /data/redis-cluster/redis-7006/redis.conf每个节点独立目录日志、AOF、RDB、nodes.conf 都落在各自目录里排查时只需要看对应目录。端口规划的意义在于集群节点间除了常规连接还要在客户端端口 10000 的位置单独开一条总线通道所以端口尽量保持这个规律不要随手乱写。看到 17001 就立刻知道它对应 7001 的集群通信运维时省心很多。2.4 用 docker run 还是 docker-compose简单验证可以直接 docker run一条命令一个容器思路清晰。但如果要反复重建我强烈建议用 docker-compose 管理。把所有配置、端口、volume 写成一个 yaml 文件本身就是一份“可执行文档”换台机器或者改拓扑改几行就能复现。bridge 模式下如果要固定 IP可以在 compose 里定义一个自定义网络并指定 ipam 网段每个服务再分配固定 IP。host 模式就不需要这些直接把 network_mode 设成 host。两种模式用同一份 compose 模板切换差别只在一个字段这也是容器化带来的好处。3. 一步步把集群拉起来3.1 redis.conf 里每个关键配置都是干嘛的我给每个节点准备的配置基本一样只有端口不同。核心内容如下port 7001 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes appendfilename appendonly.aof protected-mode no bind 0.0.0.0逐条说明理解之后才好根据自己场景调整。cluster-enabled yes 是总开关。不打开它即使你有 6 个 Redis 实例它们也只是 6 个互相独立的普通服务永远组不成集群。cluster-config-file nodes.conf 是集群状态文件。Redis 会把当前节点的 ID、集群拓扑、槽位分配等状态自动写进这个文件。这个文件非常容易成为隐藏炸弹容器删除重建后如果文件里记录的是旧 IP节点就一直卡在 handshake。所以测试环境重建节点前我习惯把对应目录里的 nodes.conf 直接删掉让 Redis 重新生成。cluster-node-timeout 5000 表示 5 秒内联系不上某个节点就判定它疑似故障。本地测试把超时调短做故障切换实验不用干等 15 秒生产环境反而要稍微调大些避免网络抖动就触发主从切换。appendonly yes 开启 AOF 持久化配合 RDB 保证节点重启后数据尽量不丢。集群模式下每个节点各自持久化不存在“多节点共享一块磁盘”这种设计所以每台机器都要持久化配置。protected-mode no 配合 bind 0.0.0.0是为了让容器外部和跨节点访问方便。这只是测试环境图省事生产绝对不能裸奔密码、网段限制、防火墙规则一个都不能少。3.2 用 docker run 启动 6 个节点以 host 网络为例启动第一个节点mkdir -p /data/redis-cluster/redis-7001 docker run -d --name redis-7001 --network host \ -v /data/redis-cluster/redis-7001:/data \ redis:7.2-alpine \ redis-server /data/redis.conf注意这里挂载的是整个 /data 目录不是单独的 redis.conf 文件。因为 Redis 运行时不光读配置还会在 /data 下写 AOF、RDB、nodes.conf挂载整个目录才能把这些状态文件也持久化到宿主机。只挂载单个配置文件的话容器一删节点身份和槽位信息全没又要重新初始化。剩下的 5 个节点把端口和容器名依次替换成 7002 到 7006命令结构完全一样。启动顺序不需要刻意控制反正最后创建集群时节点会统一握手。3.3 一条命令创建集群但要看清输出6 个容器都起来后随便进入一个节点执行docker exec -it redis-7001 redis-cli --cluster create \ 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \ 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \ --cluster-replicas 1--cluster-replicas 1 的意思是每个主节点带 1 个从节点。6 个实例会自动被编排成 3 主 3 从主从怎么搭配、谁当 master由脚本自动决定。命令执行后redis-cli 会打印一份拟分配的槽位方案并停下来问Can I set the above configuration? (type yes to accept):这一步别急着敲 yes先看一遍输出。输出里每一行都会标明哪个节点成为 master、分到哪些槽位范围哪个节点是哪个 master 的 replica。我见过有人不看直接回车结果发现 slave 全挂到一个主节点上拓扑完全不符合预期还得重建。确认无误再输入 yes。创建成功后的最后一行是[OK] All 16384 slots covered这行是验收信号代表 16384 个槽位已经全部有主节点负责。如果没看到这行集群状态多半不健康后面的读写请求会大面积报错。3.4 手动控制槽位add-node 和 reshard 的场景一键创建适合大多数场景但如果你希望精确控制每个主节点手里的槽位范围可以改用 add-node 将节点逐台加入再用 reshard 手动搬槽位。实际运维中集群扩容时新增节点是常事手动分片这步躲不开所以即使现在用不到也建议理解原理。reshard 交互会问你准备搬多少个槽位、接收槽位的节点 ID、从哪些节点搬。我第一次操作时搬 1000 个槽位输出会实时显示每个 key 的迁移进度全程不需要停机。这个机制底层是按槽位粒度迁移数据目标节点拿到槽位对应的一批 key 后源节点释放槽位客户端感知不到明显中断。4. 验证集群真的“能用”4.1 cluster nodes 输出怎么读集群建好后第一步看状态redis-cli -p 7001 cluster info重点看 cluster_state。状态是 ok说明槽位完整、节点互通如果显示 fail说明有槽位无人负责或者有节点不可达此时客户端会对写入请求直接报错。再执行redis-cli -p 7001 cluster nodes输出会列出所有节点。每一行包含节点 ID、IP 和端口、角色标志、负责的槽位范围。比如某个主节点后面跟着[0-5460]就代表它负责从 0 到 5460 这一段槽位。看到 3 个主节点各带一段连续槽位并且它们的 replica 行正确指向对应的 master ID说明拓扑和预期一致。我当时还顺手做了一个检查在所有主节点上都执行 dbsize 和 cluster countkeysinslot确认槽位对应的 key 数量合理避免数据全部挤在一个节点上的假集群。4.2 用 -c 参数体验“自动跳转”普通连接下向某个节点写入不属于它槽位的 key会直接得到 MOVED 错误。真正的集群客户端收到 MOVED 后会重新解析槽位映射并跳转到正确节点。命令行里最直观的方式是加 -c 参数redis-cli -c -p 7001 127.0.0.1:7001 set user:1001 hello - Redirected to slot [13759] located at 127.0.0.1:7003 OK看到 Redirected 就说明集群在正常工作。这个 key 的哈希槽落在 7003 上客户端自动重试到正确节点完成写入。读取类似key 不在当前节点时同样会跳转。这里要提醒一个业务层面的坑mget、mset 这类多 key 操作如果 key 不在同一个槽位集群会直接报 CROSSSLOT 错误。因为这类操作必须保证所有 key 落在同一个节点上。解决方案是用哈希标签比如 user:{1001}:info 和 user:{1001}:cart只有花括号里的 {1001} 参与哈希计算相关 key 就能落到同一个槽位。这个设计直接影响业务建模越早想清楚越好。4.3 真实演练一次主从切换光看状态不够我做了破坏性测试验证高可用。随便停掉一台主节点比如 7001docker stop redis-7001等 cluster-node-timeout 的时间过去后再看 cluster nodes会发现原来的从节点 7004 变成了 master集群状态也从临时异常恢复成 ok。故障期间如果数据还没来得及同步到从节点会存在少量丢失这是异步复制的固有权衡生产评估时要接受这个窗口。把 7001 重新 docker start 拉起来之后它会自动作为从节点挂到新 master 下面不会出现两个主节点同时存在的脑裂情况。测完我又手动执行了一次 failover 命令把主角色切回到原节点保持后续演示拓扑干净。4.4 扩容往集群加一个新分片扩容是集群绕不开的操作正好顺手演示一遍。准备第 7 个节点比如 7007先按同样的方式启动容器然后执行redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001第一个参数是新节点第二个参数是集群里任意一个已有节点。加进来后7007 会成为一个没有任何槽位的主节点此时集群槽位总数还是 16384没有实际变“大”。真正搬数据要靠 reshardredis-cli --cluster reshard 127.0.0.1:7001按提示输入要迁移的槽位数量、接收槽位的节点 ID、源节点 IDRedis 就会把对应槽位和 key 从旧节点搬到 7007。这个过程可以热执行但我建议在业务低峰期操作因为批量迁移会占用源和目标节点的内存与 CPU。如果还需要给新主节点补一个从节点继续 add-node 一个第八个节点然后 cluster replicate 指定 master 的节点 ID 就行。5. 排坑实录与注意事项5.1 一张表看懂最常见的报错现象原因解决方法启动后 cluster_state 显示 fail槽位未完全分配或某个主节点不可达cluster info 查看槽位覆盖情况修复节点或用 reshard 补齐创建集群报 ERR Slot 0 is already busy节点残留历史槽位信息清空该节点 /data 下的 nodes.conf 或换新目录节点一直显示 handshakenodes.conf 记录的是旧 IP删除节点目录下 nodes.conf重启容器总线握手失败bridge 网络下忘了映射 10000 端口补映射 17001 等总线端口或直接换 host 网络多 key 命令报 CROSSSLOTkey 不在同一槽位业务改用哈希标签 {tag}带密码创建集群失败--cluster create 没带认证参数命令里加 -a 密码主从切换后新主无法同步没有配置 masterauth所有节点统一配置 requirepass 和 masterauth表格里每一行我基本都亲手踩过。尤其是 ERR Slot busy 和 handshake 这两个最容易在“删容器重建”时出现。核心原因都是 nodes.conf 里的旧状态和当前网络环境不匹配处理方式很简单清掉重来。这也是为什么刚才强调 volume 挂载和目录隔离排查时能精确找到对应节点的状态文件。5.2 生产环境一定要补的配置本地测试可以裸奔生产不能。设置密码时有个特别容易漏掉的点每个节点都必须配 requirepass同时还要配 masterauth。因为主从切换后新提升的 master 需要让其他副本连接自己如果只有 requirepass 而没有 masterauth从节点拿着旧密码去认证新主会一直同步失败集群逐渐变“假”。创建集群时也要带认证信息命令类似redis-cli --cluster create ... -a 你的密码另外cluster-require-full-coverage 这个参数默认是 yes含义是只要有一段槽位无人负责整个集群就拒绝服务。有些团队改成 no 追求“部分可用”但代价是请求可能打到没有负责节点的槽位直接报错需要业务能容忍。对多数一致性要求高的核心链路保持默认更稳。安全方面protected-mode no 绝不等于可以裸奔生产环境必须在防火墙、安全组层面把 7001 到 7006、17001 到 17006 这些端口限制在可信网段再配合密码保护才过得去。5.3 容量和网络规划时的一点感悟3 主 3 从只是能运行的“最小配置”不是生产标配。生产环境建议主从分在不同物理机、不同机架避免一台宿主挂了把一个分片的所有副本一起带走。主从同机意味着只要机器断电整个分片就没了集群再“高可用”也扛不住这种单点。内存规划上单节点内存不建议超过宿主物理内存的 60% 到 70%要给操作系统、持久化、可能的备份留余地。而且 Redis 命令处理基本是单线程QPS 瓶颈往往先出现在单核 CPU 上不是内存容量所以别盲目堆大内存先算清楚单分片的读写需求。集群规模大了之后reshard 迁移槽位会同时占用源节点和目标节点的 CPU 与网络 IO。平时 cluster bus 的流量不大但大批量搬槽位时网卡可能成为瓶颈规划带宽和机器规格时不能只按常态流量估算。6. 最后分享两个小经验这套环境现在被我当成“缓存演练场”留着想验证什么拓扑、什么参数都是删容器几分钟重来。个人最大的体会是Docker 搭集群省的不是“第一次建起来”的时间而是“反复折腾”的时间。配置改错了、拓扑搞坏了删掉重建的成本几乎为零这让我更敢去尝试各种参数组合也更敢在测试时做各种破坏性实验。第二个经验是顺手把启动和初始化全部脚本化。我写了一个 init.sh里面先循环创建目录、启动 6 个容器再 sleep 几秒等容器就绪最后自动执行 redis-cli --cluster create并且判断输出里是否出现 All 16384 slots covered。之后不管是给同事演示还是自己重来都只需要跑一个脚本。复杂操作一旦验证可行就尽快固化下来别让经验只停留在脑袋里这是我觉得这次折腾最值回票价的地方。