☰
Docker 搭建 Redis Cluster 实战:从分片原理到高可用排坑
2026/10/19 12:21:39 网站建设 项目流程

开局先坦白一下:把这套环境跑通之前,我一直觉得 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 真实演练一次主从切换

光看状态不够,我做了破坏性测试验证高可用。随便停掉一台主节点,比如 7001:

docker 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,没有实际变“大”。真正搬数据要靠 reshard:

redis-cli --cluster reshard 127.0.0.1:7001

按提示输入要迁移的槽位数量、接收槽位的节点 ID、源节点 ID,Redis 就会把对应槽位和 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。之后不管是给同事演示还是自己重来,都只需要跑一个脚本。复杂操作一旦验证可行就尽快固化下来,别让经验只停留在脑袋里,这是我觉得这次折腾最值回票价的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询