在docker环境部署redis cluster,支持最新版redis,高可用

Redis Cluster 部署说明(三台独立云主机 + Docker,bridge + 端口映射)

目标拓扑:3 主 + 3 从,每台云主机跑 2 个 Redis 节点(1 主 + 1 个其他分片的从),
任意一台主机宕机仍能故障转移。不使用 Sentinel——故障转移由集群协议自身完成。

示例环境(下文统一使用这组私网 IP 做演示):

主机私网 IP节点1(客户端/总线)节点2(客户端/总线)角色
主机 A172.16.0.226379 / 163796380 / 16380主(0-5460) + 从(复制 C 的主)
主机 B172.16.0.236379 / 163796380 / 16380主(5461-10922) + 从(复制 A 的主)
主机 C172.16.0.246379 / 163796380 / 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;示例口令仅作演示,生产必须把 .envREDIS_PASSWORD 换成你自己的强口令,且绝不暴露到公网(仅靠安全组限制私网)。
  • 运维需自备:无内置异地备份、无监控;建议接 redis_exporter + Prometheus,重点告警 cluster_statecluster_slots_failconnected_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_IPREDIS_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 端口,公网一律拒绝:

  • 63796380(客户端端口,节点1 / 节点2)
  • 1637916380(集群总线 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 为例)。脚本读取本机 .envREDIS_HOST_A/B/C 连接全部 6 节点,在哪台执行都一样。

# 操作节点:任选一台主机(以 A 为例),全自动,无需手动确认
bash create-cluster.sh

建簇脚本会自动读取本机 .envREDIS_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:ok
  • cluster_slots_assigned:16384
  • cluster_known_nodes:6
  • cluster_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',与 .envREDIS_PASSWORD 一致;若你改过口令,请同步替换下面所有命令里的该值)。
能正常读写即说明集群可用。下面两节把「高可用」与「数据完整性」的验证方法讲清楚,建议上线前各演练一遍。

4.4 高可用验证(灾难性故障模拟演练)

本节不再定义快捷别名,所有命令直接带明文口令操作。当前口令为 +biR4+vCwbf=MxkM3aSPCZl5yh^b(与 .envREDIS_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_stateok、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:0cluster_slots_pfail:0cluster_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.sh
    • REDIS_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(与 .envREDIS_PASSWORD 一致;若你改过,下面所有示例同步替换)
  • 镜像/版本swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/redis:8.10.0

7.1 连接前三要点

  1. 必须用集群感知客户端redis-cli 要带 -c;应用要用各语言的 Cluster 模式。普通单机客户端直连会收到 MOVED / CROSSSLOT 错误。
  2. 只给「种子节点」即可,无需列全 6 个:客户端连上任一节点后,会自动通过 CLUSTER SLOTS 发现全部 6 节点与槽映射。建议给 2~3 个分布在不同主机的节点(如 22:6379、23:6379、24:6379),这样即便某台主机宕机,客户端仍能发现集群。
  3. 统一口令:所有节点 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 直连会频繁 MOVEDredis-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 的客户端。
  • 连接池与超时:高并发场景配置合理连接池与超时;JedisClustermaxAttempts 决定自动重试次数(在重定向 / 故障转移窗口期很有用)。

下载文件

先下载文件,再复现文档中部署流程https://atomgit.com/zhudev2026/redis-deploy/tree/main/docker/redis-cluster

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值