Redis Cluster 部署说明(三台独立云主机 + Docker,bridge + 端口映射)
目标拓扑:3 主 + 3 从,每台云主机跑 2 个 Redis 节点(1 主 + 1 个其他分片的从),
任意一台主机宕机仍能故障转移。不使用 Sentinel——故障转移由集群协议自身完成。
示例环境(下文统一使用这组私网 IP 做演示):
| 主机 | 私网 IP | 节点1(客户端/总线) | 节点2(客户端/总线) | 角色 |
|---|---|---|---|---|
| 主机 A | 172.16.0.22 | 6379 / 16379 | 6380 / 16380 | 主(0-5460) + 从(复制 C 的主) |
| 主机 B | 172.16.0.23 | 6379 / 16379 | 6380 / 16380 | 主(5461-10922) + 从(复制 A 的主) |
| 主机 C | 172.16.0.24 | 6379 / 16379 | 6380 / 16380 | 主(10923-16383) + 从(复制 B 的主) |
副本跨主机环形:A 的主 ← B 的从、B 的主 ← C 的从、C 的主 ← A 的从。
1. 介绍
1.1 当前方案能实现什么
- Redis Cluster 原生分片 + 高可用:6 个 Redis 实例组成集群,16384 个哈希槽均匀分到 3 个主节点,数据按 key 哈希自动分片。
- 自动故障转移(无需 Sentinel):每个主节点有 1 个副本(从节点);主宕机时副本自动提升为新主,选主与切换由集群总线 gossip 完成。
- 拓扑容灾:每台主机跑 1 主 + 1 从(从属于另一台的主)。任意单台主机宕机,其主节点的副本在另一台主机上接管,集群仍完整可用。
- 持久化:每个节点开启 AOF(
appendonly yes),数据落盘。 - 部署形态:Docker Compose 单机编排 + bridge 网络 + 宿主机端口映射;容器内部端口固定(6379 客户端 / 16379 总线),通过
--cluster-announce-*对外通告宿主机地址。
1.2 适用业务场景
- 需要 Redis 高可用(HA),且数据量 / 吞吐超过单机、需要**水平扩展(增加分片)**的业务。
- 已具备 3 台同 VPC、私网互通的云主机,但不依赖 Kubernetes / Docker Swarm,希望用纯 Docker 落地。
- 缓存、会话存储、排行榜、计数器、轻量 KV 等可容忍最终一致性与分片约束的场景。
- 希望运维简单、不引入额外组件(Sentinel / Operator)的团队。
1.3 局限性
- 至少 3 个主节点才能保证基本冗余;少于 3 主会失去 16384 槽全覆盖能力(部分 key 不可写)。
- 命令约束:Cluster 不支持跨 slot 的
MULTI/事务、SELECT切库(仅 db0);KEYS/FLUSHALL会广播到所有节点;多 key 命令(MGET/MSET/事务)要求所有 key 在同一 slot,需用 hash tag({...})约束。 - 客户端必须集群感知:须使用
redis-cli -c或 Lettuce / Jedis 的 Cluster 模式;普通单机客户端直连写会报CROSSSLOT/MOVED错误。 - 故障切换有秒级窗口:原主真正宕机到从提升之间存在短暂不可写;脑裂防护依赖
cluster-require-full-coverage(本模板默认开启)与可选的min-replicas-to-write(本模板未强制,可按需加)。 - 同时宕 2 台主机可能丢失部分 slot 覆盖(设计边界,标准 3 主集群固有)。
- 安全:本模板未启用 TLS;示例口令仅作演示,生产必须把
.env的REDIS_PASSWORD换成你自己的强口令,且绝不暴露到公网(仅靠安全组限制私网)。 - 运维需自备:无内置异地备份、无监控;建议接
redis_exporter + Prometheus,重点告警cluster_state、cluster_slots_fail、connected_slaves变化。 - 扩缩容需手动 reshard:本模板提供命令与步骤,但非一键;迁移大 key 时会占用带宽。
2. 配置
2.1 模板文件(三机共用同一份)
目录 redis-cluster/ 在三台主机上内容完全相同:
docker-compose.yml:两节点服务、端口映射、数据卷、健康检查。redis.conf:Cluster 基础配置(端口、cluster-enabled、AOF、maxmemory 等)。create-cluster.sh:组建集群(自动确认,全自动)。check-cluster.sh:一键健康检查。.env.example:配置样例。
2.2 每台主机唯一的差异:.env
三机把同一份 redis-cluster/ 拷过去后,唯一要改的是每台主机的 .env:
| 配置项 | 说明 | 三机是否相同 |
|---|---|---|
ANNOUNCE_IP | 本机私网 IP | 各不相同(A=172.16.0.22 / B=172.16.0.23 / C=172.16.0.24) |
REDIS_HOST_A/B/C | 集群三台主机私网 IP | 三机填相同值(172.16.0.22/23/24) |
REDIS_PASSWORD | 集群统一口令 | 三机必须完全相同 |
NODE1_HOST_PORT / NODE1_HOST_BUS_PORT | 节点1 宿主机端口 | 建议一致(6379 / 16379) |
NODE2_HOST_PORT / NODE2_HOST_BUS_PORT | 节点2 宿主机端口 | 建议一致(6380 / 16380) |
ANNOUNCE_IP与REDIS_HOST_A/B/C是两回事:前者只是"本机"容器对外宣告用的 IP;后者才是建簇脚本create-cluster.sh要连接的三台主机地址。
2.3 主机 A 的 .env 示例(B、C 仅 ANNOUNCE_IP 不同)
ANNOUNCE_IP=172.16.0.22
REDIS_HOST_A=172.16.0.22
REDIS_HOST_B=172.16.0.23
REDIS_HOST_C=172.16.0.24
REDIS_PASSWORD=+biR4+vCwbf=MxkM3aSPCZl5yh^b
NODE1_HOST_PORT=6379
NODE1_HOST_BUS_PORT=16379
NODE2_HOST_PORT=6380
NODE2_HOST_BUS_PORT=16380
示例口令为模板演示值,生产请替换为你自己的强随机串,并在三机保持一致。
2.4 安全组(重要)
仅允许三台私网 IP 之间入站以下 TCP 端口,公网一律拒绝:
6379、6380(客户端端口,节点1 / 节点2)16379、16380(集群总线 gossip 端口,节点1 / 节点2)
若改用其他宿主机端口,请同步放行对应端口。
3. 部署
3.1 在每台主机(A、B、C)分别执行
操作节点:三台主机 A / B / C 各自执行一遍(下面以 A(172.16.0.22) 为例,B/C 把 IP 换成各自本机)。
scp/ssh里的user@172.16.0.22即目标主机。
# 操作节点:主机 A(172.16.0.22)——以下在 A 上执行;B/C 把 IP 换成各自本机
# 把模板目录拷到本机(以 A 为例,B/C 同理换 IP)
scp -r redis-cluster/ user@172.16.0.22:~/
ssh user@172.16.0.22
cd redis-cluster
cp .env.example .env
# 编辑 .env:把 ANNOUNCE_IP 改成「本机」IP(A=172.16.0.22),其余保持与上面示例一致
vi .env
docker compose up -d
三机都执行完、docker compose ps 状态为 healthy 后,进入下一步。
3.2 任选一台主机执行一次(组建集群)
操作节点:三台主机中任选一台(A / B / C 任意;下面以 A 为例)。脚本读取本机
.env的REDIS_HOST_A/B/C连接全部 6 节点,在哪台执行都一样。
# 操作节点:任选一台主机(以 A 为例),全自动,无需手动确认
bash create-cluster.sh
建簇脚本会自动读取本机 .env 的 REDIS_HOST_A/B/C、端口与 REDIS_PASSWORD,把 6 个节点组建成 3 主 3 从。
4. 检查
节点标注约定:下文命令前的「操作节点」= 你在哪台主机上敲这条命令;「查询节点」= 连到哪个 Redis 节点查看/操作集群(命令里的
-h <ip>即所连节点)。docker exec/docker restart/docker kill/docker start这类容器操作必须在目标容器所在的主机上执行。
4.1 一键健康检查(推荐)
操作节点:任选一台主机(A / B / C 任意一台均可)。脚本读取本机
redis-cluster/.env,连接集群中任一可达节点完成检查。
bash check-cluster.sh
脚本会连接任一节点,打印完整 cluster info / cluster nodes,解析各节点角色与槽位,并自动判定:
cluster_state是否为ok- 槽位是否全覆盖
16384、无fail/pfail - 每个主节点是否至少有 1 个副本
- 主从是否跨主机(同机则告警)
- 节点是否掉线 / 处于
fail状态
结尾给出「集群健康 / 发现 N 项异常」结论;异常时退出码非 0(可接入监控)。
4.2 手动核验
操作/查询节点:任选一台存活主机(下面以 172.16.0.22 为例;换成 23 / 24 任意一台,结果一致)。
# 操作/查询节点:任选存活主机(以 172.16.0.22 为例)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 cluster info
关注:
cluster_state:okcluster_slots_assigned:16384cluster_known_nodes:6cluster_size:3
再看一下拓扑与副本跨机情况:
# 操作/查询节点:同上(任选存活主机,以 172.16.0.22 为例)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 cluster nodes
4.3 验证集群「可用」(写入并跨节点读取)
操作/查询节点:写入用 172.16.0.22,跨节点读取分别连 172.16.0.23 / 172.16.0.24(命令里的
-h即所连节点;任选存活主机均可,结果一致)。
# 操作/查询节点:写入 172.16.0.22,跨节点读取 172.16.0.23 / 172.16.0.24(以 -h 指定)
# 连任意节点写几个 key(集群客户端 -c 会自动路由到对应分片)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set foo bar
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 get foo
# 写入分布在多个 slot 的 key(hash tag 让 {u1} 两个 key 同槽、{u2} 另占一槽)
# 注意:每条 set 必须单独一条 redis-cli 调用——单次调用里连写多个 set 会触发 ERR syntax error
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set '{u1}.a' 1
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set '{u1}.b' 2
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set '{u2}.a' 3
# 验证:跨节点读取(连任一节点即可,-c 自动重定向到 key 所属分片)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6379 get '{u1}.a' # 期望 1
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.24 -p 6379 get '{u2}.a' # 期望 3
# 可选:查看每个 key 实际落在哪个 slot(cluster keyslot 任一节点均可)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 cluster keyslot '{u1}.a'
说明:上面每条命令都直接带了明文口令(
-a '+biR4+vCwbf=MxkM3aSPCZl5yh^b',与.env的REDIS_PASSWORD一致;若你改过口令,请同步替换下面所有命令里的该值)。
能正常读写即说明集群可用。下面两节把「高可用」与「数据完整性」的验证方法讲清楚,建议上线前各演练一遍。
4.4 高可用验证(灾难性故障模拟演练)
本节不再定义快捷别名,所有命令直接带明文口令操作。当前口令为
+biR4+vCwbf=MxkM3aSPCZl5yh^b(与.env的REDIS_PASSWORD一致;若你部署时改过口令,请把下面每条命令里的该值替换为你自己的强口令)。命令统一形如:docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \ redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h <ip> -p <port> <命令>说明:口令写在命令行会触发 redis-cli 的
Warning: Using a password with '-a'...提示,属无害提醒可忽略;若想消除可在整条命令末尾加2>/dev/null(但同时会屏蔽真实报错,排错时不建议加)。
若报WRONGPASS:说明你替换后的口令与集群实际口令不一致。定位集群真实口令:docker inspect redis-node1 --format '{{.Config.Cmd}}'(看--requirepass后的值);要么把命令里的明文口令改成该值,要么三机docker compose up -d让容器用你当前的口令(命名卷redis-data-*数据不丢)。
目标:直接模拟某主节点突发崩溃 / 被强杀(而非优雅停机),验证集群能在无人干预下自动完成故障转移、客户端读写不中断、已复制数据不丢失。
当前拓扑环:22:6379 主 ← 23:6380 从、23:6379 主 ← 24:6380 从、24:6379 主 ← 22:6380 从。
容器名:每机redis-node1(6379,即各机的主)/redis-node2(6380,即各机的从)。
时间窗:docker kill后需等待约 15~30s(默认cluster-node-timeout=15s+ 副本选举)故障转移才会完成,之后再查。
演练对象说明:本演练只强杀主机 A 上的「主节点」容器redis-node1(6379)。注意主机 A 上另一个容器redis-node2(6380)是「24:6379 主」的副本,它不会被杀、继续存活——这正好模拟「单主机上仅主进程崩溃」的真实局部故障,而非整台机器宕机。
第 0 步 演练前基线(任意存活节点,如 node1 上执行)
先写入一个贯穿全程的探针 key(写在某主上,会自动复制到其副本),作为后续「数据不丢」比对的基准;再记录各节点数据量:
# 查询节点:172.16.0.22:6379(任选存活节点)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set ha_probe before_failover
# (可选,确认已同步到副本)docker run --rm ... -c -h 172.16.0.23 -p 6380 get ha_probe # 应返回 before_failover
bash check-cluster.sh # 先确认基线健康,结论应为"集群健康"
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster call 172.16.0.22:6379 dbsize # 记录每个节点的 dbsize 作为数据量基线
基线要点:记录 (a)
ha_probe的值before_failover;(b) 各节点 dbsize。第 3 步用它们验证「数据未丢、未损坏」。
第 1 步 模拟灾难:在主机 A 上强杀主节点容器
# 操作节点:主机 A(172.16.0.22)
ssh user@172.16.0.22
docker kill redis-node1 # SIGKILL,模拟进程崩溃 / 被 OOM 强杀(无优雅停机,比 docker stop 更严苛)
# 注:若想模拟"容器被销毁",可用 docker rm -f redis-node1(数据卷仍在,重启后数据不丢)
验证点 1(在主机 B 172.16.0.23 上查询集群状态,建议 kill 后等 15~30s 再查):
# 查询节点:172.16.0.23:6379(存活的任一节点;cluster nodes 是「全集群视图」,任意节点都能看到全局)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6379 cluster nodes | grep -E '172.16.0.22:6379|172.16.0.23:6380'
# 解读:22:6379 那一行会出现 'fail' 标记(节点不可达);其副本 23:6380 角色由 'slave' 变为 'master',
# slots 字段出现它刚接管的 '0-5460'
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6379 cluster info | grep -E 'cluster_state|cluster_slots_assigned'
# 期望:cluster_state:ok、cluster_slots_assigned:16384(槽已由新主接管,集群仍完整、可写)
若 30s 后 22:6379 仍未标记
fail、或 23:6380 未提升为master:检查三机间 16379/16380 总线端口是否互通、cluster-node-timeout是否被改大;必要时延长等待或排查网络。
第 2 步 故障期间读写验证(证明集群不中断)
# 查询节点:172.16.0.23:6380(已提升为新主,存活节点)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 set probe_during_failover ok
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 get probe_during_failover # 应返回 ok
说明:写请求连到新主成功并返回
ok,证明即使原主崩溃,客户端仍能正常读写,集群对外不中断。
第 3 步 数据不丢验证(对比第 0 步基线)
# 查询节点:新主 172.16.0.23:6380(已接管 0-5460,且故障前本就是 22:6379 的副本)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 dbsize # 应与第 0 步 22:6379 的 dbsize 一致(含 ha_probe 那条)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 get ha_probe # 应仍为 before_failover(未损坏、未丢)
说明:23:6380 在故障前已是 22:6379 的副本,复制了全部已同步数据;故障切换到它后,探针 key 与原主数据一致,证明「已复制的数据不丢、不损坏」。(注意:故障瞬间主刚 ack 但还没同步到副本的写会丢失,这是异步复制的固有边界,非演练失败。)
第 4 步 恢复:把被强杀的节点重新拉起
# 操作节点:主机 A(172.16.0.22)
ssh user@172.16.0.22
docker start redis-node1
验证点 4(在主机 A 本地查询):
# 查询节点:172.16.0.22:6379
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 cluster nodes | grep '172.16.0.22:6379'
# 期望:22:6379 重新 connected,角色变为 'slave'(复制新主 23:6380),原 0-5460 槽已不归它
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 cluster info | grep cluster_state # 期望 ok
说明:被强杀容器的数据卷(命名卷
redis-data-*)完好,重启后节点带着原数据重新加入,并以「从」身份从新主做一次增量 / 全量同步,磁盘上已持久化的数据不会丢。
第 5 步 收尾全量健康检查
# 操作节点:任一主机(如 node1)
bash check-cluster.sh # 期望:结论"集群健康,所有检查通过"
演练完整通过标准:
- 第 1 步后约 30s 内:死节点标记
fail、其副本自动提升为master并接管槽、cluster_state仍ok、16384 槽全覆盖;- 第 2 步:故障期间
set/get正常,集群读写不中断;- 第 3 步:新主
dbsize与基线一致、探针 key 值不变(数据不丢、不损坏);- 第 4 步:原主
docker start后以slave身份重新connected回归;- 第 5 步:
bash check-cluster.sh全量检查通过。
4.5 数据完整性与持久化验证
要点:复制是异步的——主刚 ack 的写若还没同步到副本,主瞬间宕机会丢失这「最后一小段写」。下面方法用于验证「已复制的数据不丢、不损坏」,以及「落盘持久化可靠」。
1) 写入已知数据集,记录基线
操作/查询节点:任选存活主机(以下以 172.16.0.22 为例)。
# 操作/查询节点:任选存活主机(以 172.16.0.22 为例)
# 写 1000 个 key(无 hash tag,自然分布到不同分片)。
# 通过管道一次性灌入,只启动一次容器,避免反复 docker run 的耗时。
for i in $(seq 1 1000); do printf 'SET k%s v%s\n' "$i" "$i"; done \
| docker run --rm -i swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli \
-a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 >/dev/null
# 记录各节点 key 数(call 会广播到所有节点)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster call 172.16.0.22:6379 dbsize
# 抽样记录值,供事后比对
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 get k1 # 记下来应为 v1
2) 验证「主 → 从」复制到位
操作/查询节点:副本 172.16.0.23:6380(复制 22:6379 的主);也可查其它副本。
# 操作/查询节点:副本 172.16.0.23:6380(复制 22:6379 的主)
# 在副本上确认已追上主(master_link_status:up,offset 接近)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 info replication
# 副本 dbsize 应 = 其主 dbsize
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 dbsize
要让「写」在宕机前确已落到副本,可在应用层用 WAIT:
# 操作/查询节点:主 172.16.0.22:6379(写 + 等待副本确认)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set k1 v1
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 wait 1 0 # 阻塞直到至少 1 个副本确认(实现强一致写)
3) 故障转移后「数据不丢」验证
操作/查询节点:新主 172.16.0.23:6380(4.4 演练中接管 0-5460 的节点;若演练对象不同,换成实际提升的新主)。
在 4.4 的停机演练前后,对比基线:
# 操作/查询节点:新主 172.16.0.23:6380(转移后接管 0-5460 的节点)
# 故障转移完成后(副本已变主),在其上查:
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 dbsize # 应与转移前主节点的 dbsize 一致
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6380 get k1 # 应仍为 v1(未损坏、未丢)
若 k1 这种「已写入且已复制」的 key 在转移后取回的值与基线一致、dbsize 不变,即证明数据在切换中无丢失、无损坏。
4) 防「物理损坏」:AOF / RDB 校验工具
操作节点:待校验节点所在的主机(下面以 主机 A 172.16.0.22 上的
redis-node1为例;要校验其它节点,请到对应主机执行,或把容器名换成redis-node2)。
# 操作节点:节点所在主机(以主机 A 172.16.0.22 上的 redis-node1 为例)
# 进入某节点容器,对持久化文件做完整性校验(建议先 cp 出来再校验,避免动线上文件)
docker exec -it redis-node1 sh -c 'redis-check-aof --fix /data/appendonlydir/appendonly.aof.* 2>&1 | head'
docker exec -it redis-node1 sh -c 'redis-check-rdb /data/dump.rdb 2>&1 | head'
# 输出 "AOF is valid" / "RDB looks OK" 即通过
5) 持久化落盘验证(进程重启不丢)
操作节点:节点所在主机(以 主机 A 172.16.0.22 为例,重启其
redis-node1);查询节点:重启后连 172.16.0.22:6379。
# 操作节点:主机 A 172.16.0.22(重启其 redis-node1);查询节点:172.16.0.22:6379
docker restart redis-node1
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 dbsize # AOF 重载后应等于重启前,证明数据已落盘
6) 集群级健康兜底
cluster_slots_fail:0、cluster_slots_pfail:0、cluster_state:ok 三者同时成立,说明没有任何槽处于异常/不可达状态,即集群数据面完整。bash check-cluster.sh 已自动覆盖前两项与跨机副本检查。
5. 扩容(新增多台云主机,如 D、E)
5.1 配置变化
- 新主机同样部署
redis-cluster/;.env里:ANNOUNCE_IP= 新机私网 IP(如 D=172.16.0.25)REDIS_HOST_A/B/C仍填原三台(172.16.0.22/23/24)——建簇脚本只用于初始 6 节点;扩容用下面的add-node,不再跑create-cluster.shREDIS_PASSWORD与现有集群完全一致- 端口仍 6379/16379、6380/16380
- 安全组:把新主机私网 IP 加入「允许 6379/6380/16379/16380 互访」白名单,新主机也要允许来自其余主机的这些端口。
5.2 操作步骤(在新主机 D 上)
# 1) 拷模板、改 .env(ANNOUNCE_IP=172.16.0.25)、拉起空节点
# 操作节点:新主机 D(172.16.0.25)——从你的工作站 scp 过去并 ssh 登录后执行
scp -r redis-cluster/ user@172.16.0.25:~/ && ssh user@172.16.0.25
cd redis-cluster && cp .env.example .env && vi .env && docker compose up -d
# 2) 把新节点作为「新主」加入集群
# 操作节点:任意现节点所在主机(以 172.16.0.22 为例);命令连接 172.16.0.25:6379(新节点)与 172.16.0.22:6379(现节点)
# 语法:add-node <新节点地址> <集群中任一现节点地址>(命令中的明文口令需与你的 .env 一致)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster add-node 172.16.0.25:6379 172.16.0.22:6379
# 3) 把一部分 slot 从现有主迁移到新主
# 操作节点:任选存活主机(以 172.16.0.22 为例);迁移目标为新主 172.16.0.25:6379
# 交互式,按提示输入源主 ID、目标新主 ID、要迁移的 slot 数量
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster reshard 172.16.0.22:6379
# (也可加为某现主的「从」只增加冗余、不扩容量:add-node 后用 CLUSTER REPLICATE <目标主ID>)
# 4) 验证
bash check-cluster.sh
新主机上跑几个节点由你定(1 个纯扩容,或 2 个 = 1 主 + 1 从);建议主、从尽量分散在不同物理主机,避免同机主从。
6. 缩容(减少云主机,如下线主机 C)
必须先迁移 slot、移除节点,再下线主机;否则持 slot 的主消失会导致对应数据分片不可用。
6.1 配置变化
- 安全组:把下线主机的 IP 从互访白名单移除。
- 其余主机的
.env无需改动(REDIS_HOST_A/B/C仍指向存活主机即可;若彻底移除某 IP,后续建簇才需改,缩容本身不改)。
6.2 操作步骤(以下线主机 C = 172.16.0.24 为例)
# 1) 对 C 上的每个主节点(此处是 172.16.0.24:6379,持有 10923-16383),先把它的 slot 迁到其它存活主:
# 操作节点:任选存活主机(以 172.16.0.22 为例,连接其 6379);迁移对象是 C 的主 172.16.0.24:6379 的 slot
# (命令中的明文口令需与你的 .env 一致)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster reshard 172.16.0.22:6379
# 按提示把源主(172.16.0.24:6379)的所有 slot 迁到某个存活主,直到该主不再持有任何 slot
# 2) 确认 C 上的节点已无 slot(cluster nodes 中其 slots 字段为空),再逐个 del-node:
# 操作节点:任选存活主机;del-node 目标为 C 的节点 172.16.0.24:6379 / :6380(<节点ID> 用 cluster nodes 查得)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster del-node 172.16.0.24:6379 <节点ID>
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c --cluster del-node 172.16.0.24:6380 <节点ID>
# 3) 节点全部移除后,才能安全下线 / 回收该云主机
# 4) 验证
bash check-cluster.sh
Redis Cluster 不要求奇数节点 / quorum(不同于 Sentinel),只要 16384 个 slot 都被存活主覆盖即可。生产环境至少保留 3 个主节点,以保证 slot 均匀分布与冗余。
7. 使用和访问(客户端如何连接 Redis Cluster)
集群已通过宿主机私网 IP + 端口映射对外暴露;任何能访问这三台私网的主机/容器,都可以像连单机 Redis 一样连上来——前提是用「集群模式」客户端。本章给出 Docker 容器内 redis-cli 与主流编程语言的连接示例。
连接要素(三机统一):
- 种子节点(私网,安全组已放行):
172.16.0.22:6379/172.16.0.23:6379/172.16.0.24:6379(也可带 6380;建议至少给 2 个不同主机的节点做种子)- 口令:
+biR4+vCwbf=MxkM3aSPCZl5yh^b(与.env的REDIS_PASSWORD一致;若你改过,下面所有示例同步替换)- 镜像/版本:
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0
7.1 连接前三要点
- 必须用集群感知客户端:
redis-cli要带-c;应用要用各语言的 Cluster 模式。普通单机客户端直连会收到MOVED/CROSSSLOT错误。 - 只给「种子节点」即可,无需列全 6 个:客户端连上任一节点后,会自动通过
CLUSTER SLOTS发现全部 6 节点与槽映射。建议给 2~3 个分布在不同主机的节点(如 22:6379、23:6379、24:6379),这样即便某台主机宕机,客户端仍能发现集群。 - 统一口令:所有节点
requirepass相同,客户端只需配一个密码。
7.2 以 Docker 容器为例:用 redis-cli 连集群
最轻量的验证方式——拉一个与集群同版本的 redis 镜像,起个临时容器,用集群模式(-c)连种子节点。读写会自动重定向到 key 所属分片。
# 操作节点:任意能访问三台私网的主机(如运维机 / 某台云主机)
# 单条命令形式(连 22:6379 写,会自动路由到正确分片)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379 set hello world
# 连「另一台」节点也能取到(集群模式自动 MOVED 重定向到 hello 所在分片)
docker run --rm swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.23 -p 6379 get hello # 期望 world
交互式(进入容器连续操作):
docker run --rm -it swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0 \
redis-cli -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' -c -h 172.16.0.22 -p 6379
# 进入后可直接 get / set / cluster info 等;-c 保证跨槽 key 自动跳转
-c的关键作用:不带-c的 redis-cli,遇到 key 不在当前直连节点时会报MOVED <slot> <ip:port>并停止;加-c后会自动跟随重定向完成读写。
在集群所在主机的已运行容器里操作:若你的客户端和 redis 在同一台云主机,可直接 docker exec 进节点容器(用容器名 redis-node1/redis-node2):
# 操作节点:节点所在主机(以主机 A 172.16.0.22 为例)
docker exec -it redis-node1 redis-cli -c -a '+biR4+vCwbf=MxkM3aSPCZl5yh^b' get hello
注意:本集群节点对外通告的是宿主机私网 IP(
--cluster-announce-ip)。跨主机的客户端必须连「宿主机 IP + 映射端口(6379/6380)」,不能连容器内部 IP;同一主机内docker exec用容器名即可。
7.3 应用代码连接示例(集群模式)
下面示例都只填种子节点 + 口令,客户端自动完成拓扑发现与重定向。
7.3.1 Java — Jedis
import redis.clients.jedis.*;
import java.util.HashSet;
import java.util.Set;
Set<HostAndPort> seeds = new HashSet<>();
seeds.add(new HostAndPort("172.16.0.22", 6379));
seeds.add(new HostAndPort("172.16.0.23", 6379));
seeds.add(new HostAndPort("172.16.0.24", 6379));
JedisCluster jedis = new JedisCluster(
seeds,
2000, // connectionTimeout(ms)
2000, // soTimeout(ms)
5, // maxAttempts(故障转移期间重试)
"+biR4+vCwbf=MxkM3aSPCZl5yh^b", // password
new JedisPoolConfig());
jedis.set("foo", "bar");
System.out.println(jedis.get("foo")); // bar
jedis.close();
7.3.2 Java — Lettuce(基于 Netty,更省连接,推荐)
import io.lettuce.core.*;
import io.lettuce.core.cluster.*;
RedisClusterClient client = RedisClusterClient.create(java.util.Arrays.asList(
RedisURI.Builder.redis("172.16.0.22", 6379).withPassword("+biR4+vCwbf=MxkM3aSPCZl5yh^b").build(),
RedisURI.Builder.redis("172.16.0.23", 6379).withPassword("+biR4+vCwbf=MxkM3aSPCZl5yh^b").build(),
RedisURI.Builder.redis("172.16.0.24", 6379).withPassword("+biR4+vCwbf=MxkM3aSPCZl5yh^b").build()
));
StatefulRedisClusterConnection<String, String> conn = client.connect();
conn.sync().set("foo", "bar");
System.out.println(conn.sync().get("foo")); // bar
conn.close();
client.shutdown();
7.3.3 Python — redis-py(>=4.0 内置 Cluster 支持)
from redis.cluster import RedisCluster
rc = RedisCluster(
host="172.16.0.22", port=6379,
password="+biR4+vCwbf=MxkM3aSPCZl5yh^b",
# 等价写法:startup_nodes=[{"host":"172.16.0.22","port":6379}, ...]
decode_responses=True,
)
rc.set("foo", "bar")
print(rc.get("foo")) # bar
7.3.4 Node.js — ioredis(Cluster 模式)
const Redis = require("ioredis");
const cluster = new Redis.Cluster(
[
{ host: "172.16.0.22", port: 6379 },
{ host: "172.16.0.23", port: 6379 },
{ host: "172.16.0.24", port: 6379 },
],
{
redisOptions: { password: "+biR4+vCwbf=MxkM3aSPCZl5yh^b" },
scaleReads: "slave", // 读请求分摊到从节点(默认 master)
}
);
await cluster.set("foo", "bar");
console.log(await cluster.get("foo")); // bar
7.3.5 Go — go-redis
import (
"context"
"github.com/redis/go-redis/v9"
)
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"172.16.0.22:6379",
"172.16.0.23:6379",
"172.16.0.24:6379",
},
Password: "+biR4+vCwbf=MxkM3aSPCZl5yh^b",
})
ctx := context.Background()
rdb.Set(ctx, "foo", "bar", 0)
val, _ := rdb.Get(ctx, "foo").Result()
println(val) // bar
7.4 连接注意事项与常见坑
- 必须用集群模式:上面每个客户端都显式启用 Cluster;用单机 client 直连会频繁
MOVED。redis-cli记得带-c。 - 种子节点要跨主机:只填一台主机的两个端口不够——那台主机宕机后客户端就发现不了集群。至少填 2 个分布在不同主机的节点。
- 多 key 命令必须同槽:
MGET/MSET/ 事务 / 交集等涉及多个 key 时,所有 key 必须在同一 slot,否则报CROSSSLOT。用 hash tag{...}把相关 key 锁到同一槽,例如{user:1001}.name、{user:1001}.age。 - 只访问 db0:Cluster 不支持
SELECT,所有 key 都在 0 号库。 - 读从节点(最终一致):默认读写都走主;想让读分摊到从,Jedis/Lettuce 用
ReadFrom/readonly,ioredis 用scaleReads。注意从是异步复制,读到的是「最终一致」数据,可能落后主一点点。 - 公网安全:本集群未启用 TLS,口令为明文演示值。只允许私网访问(靠安全组);切勿把 6379/6380 暴露到公网。生产请换强口令,并考虑在前面加 TLS 代理(如 stunnel / nginx-stream)或用支持 TLS 的客户端。
- 连接池与超时:高并发场景配置合理连接池与超时;
JedisCluster的maxAttempts决定自动重试次数(在重定向 / 故障转移窗口期很有用)。
下载文件
先下载文件,再复现文档中部署流程https://atomgit.com/zhudev2026/redis-deploy/tree/main/docker/redis-cluster

4490

被折叠的 条评论
为什么被折叠?



