Skip to content

Redis Cluster:六节点搭建与槽位迁移 ​

单机内存到顶、写入 QPS 到顶时,才轮到 Cluster。它换来的是水平扩展,代价是你不能再用多 key 原子操作、不能 select 库、客户端要支持 MOVED/ASK。

场景:什么时候真的需要 Cluster ​

信号说明
数据量 > 单机内存 60%再涨就要频繁淘汰,或 fork 时被打爆
写 QPS > 单实例上限单线程 Redis 写 QPS 通常在 8~10 万(pipeline 另算)
需要不停机扩容加机器就能加容量

如果只是"读多",主从 + 哨兵就够了,别上 Cluster——运维复杂度和客户端限制不值这个钱。

一、拓扑 ​

最小可用:3 主 3 从,主从交叉分布在不同机器上。

机器端口角色
10.0.0.117000master
10.0.0.117001replica(跟 10.0.0.12 的 master)
10.0.0.127002master
10.0.0.127003replica(跟 10.0.0.13 的 master)
10.0.0.137004master
10.0.0.137005replica(跟 10.0.0.11 的 master)

Cluster 模式下 16384 个 slot 分给 3 个主,每个主约 5461 个。key 落在哪个槽由 CRC16(key) mod 16384 决定。

二、配置文件 ​

每个实例一份,改端口和路径:

ini
# /etc/redis/7000.conf
port 7000
bind 0.0.0.0
protected-mode no
daemonize no
supervised systemd
dir /var/lib/redis/7000
logfile /var/log/redis/7000.log
pidfile /run/redis/7000.pid
requirepass YourStrongPass
masterauth YourStrongPass

# Cluster 必备
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 15000
cluster-announce-ip 10.0.0.11
cluster-announce-port 7000
cluster-announce-bus-port 17000

# 不用额外开端口,Cluster 总线是 port + 10000
appendonly yes
appendfsync everysec
bash
mkdir -p /var/lib/redis/{7000,7001,7002,7003,7004,7005}
chown -R redis:redis /var/lib/redis

关键配置解释:

配置为什么必须
cluster-enabled yes不开就是普通单机模式
cluster-config-file节点自己维护的槽位/节点表,不能手改
cluster-node-timeout 1500015 秒无心跳判下线。太短会误判,太长切换慢
cluster-announce-ip官方文档没强调,但容器/多网卡下不配就会广播错误 IP,集群永远建不起来
cluster-announce-bus-port默认 port + 10000,被防火墙挡了必须显式开放

Cluster 需要两套端口

业务端口 7000,总线端口 17000(+10000)。云安全组只开 7000 是最常见的"建不起来"原因。

systemd 模板(一次生成 6 份):

ini
# /etc/systemd/system/redis-cluster@.service
[Unit]
Description=Redis Cluster Instance %i
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
User=redis
Group=redis
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/%i.conf
ExecStop=/usr/local/redis/bin/redis-cli -p %i -a YourStrongPass shutdown nosave
Restart=always
RestartSec=3
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
bash
systemctl daemon-reload
for p in 7000 7001 7002 7003 7004 7005; do systemctl enable --now redis-cluster@$p; done
for p in 7000 7001 7002 7003 7004 7005; do echo -n "$p: "; redis-cli -p $p -a '密码' ping 2>/dev/null; done

输出:

text
7000: PONG
7001: PONG
7002: PONG
7003: PONG
7004: PONG
7005: PONG

此时查集群状态还是空的:

bash
redis-cli -p 7000 -a '密码' cluster info
text
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_known_nodes:1
cluster_size:0

cluster_state:fail 是正常的——还没分配槽位。

三、创建集群 ​

bash
redis-cli -a '密码' --cluster create \
  10.0.0.11:7000 10.0.0.12:7002 10.0.0.13:7004 \
  10.0.0.11:7001 10.0.0.12:7003 10.0.0.13:7005 \
  --cluster-replicas 1

输出(关键部分):

text
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.0.0.12:7003 to 10.0.0.11:7000
Adding replica 10.0.0.13:7005 to 10.0.0.12:7002
Adding replica 10.0.0.11:7001 to 10.0.0.13:7004
...
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

解释:

  • 命令里前 3 个是 master,后 3 个是 replica,--cluster-replicas 1 让工具自动配对。
  • 自动配对是反直觉的:它会尽量把副本放到 master 之外的其他机器上。
  • [OK] All 16384 slots covered 是唯一成功标志。少一个槽都会 cluster_state:fail。

四、验证 ​

bash
redis-cli -p 7000 -a '密码' cluster info
redis-cli -p 7000 -a '密码' cluster nodes
redis-cli -p 7000 -a '密码' cluster slots
text
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1842
text
3d41b7c0e5a29f6b8c1d0e2f4a6b8c9d0e1f2a3b 10.0.0.11:7000@17000 myself,master - 0 1761549301000 1 connected 0-5460
9c2e4f1a7b3d5c8e0a2b4c6d8e0f1a3b5c7d9e1f 10.0.0.12:7002@17002 master - 0 1761549301250 2 connected 5461-10922
5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b 10.0.0.13:7004@17004 master - 0 1761549301100 3 connected 10923-16383
...

cluster nodes 每行字段:

字段含义
id40 位节点 ID
ip:port@bus业务地址和总线地址
myself,master / slave角色与是否本节点
connected 0-5460负责的槽位范围

用 --cluster check 做一次完整体检:

bash
redis-cli -a '密码' --cluster check 10.0.0.11:7000
text
10.0.0.11:7000 (3d41b7c0...) -> 0 keys | 5461 slots | 1 slaves.
10.0.0.12:7002 (9c2e4f1a...) -> 0 keys | 5462 slots | 1 slaves.
10.0.0.13:7004 (5a6b7c8d...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 10.0.0.11:7000)
[OK] All nodes agree about slots configuration.
[OK] All 16384 slots covered.

五、客户端必须带 -c ​

bash
redis-cli -c -p 7000 -a '密码' set user:1001 tom
redis-cli -c -p 7000 -a '密码' get user:1001

输出:

text
-> Redirected to slot [10006] located at 10.0.0.12:7002
OK
"tom"

不带 -c 会直接报错:

text
(error) MOVED 10006 10.0.0.12:7002

MOVED 和 ASK 的区别 ​

响应含义客户端动作
MOVED 3999 10.0.0.12:7002这个槽永久归 7002更新本地槽位表,重发
ASK 3999 10.0.0.12:7002这个槽正在迁移,这条 key 已经在目标上不更新槽位表,只对这条命令加 ASKING 后重发

ASK 只出现在槽迁移过程中,客户端如果把它当 MOVED 处理,迁移结束后会一直往旧节点发。

六、多 key 操作的硬约束 ​

bash
redis-cli -c -p 7000 -a '密码' mset k1 a k2 b
text
(error) CROSSSLOT Keys in request don't hash to the same slot

MSET、MGET、SUNION、事务、Lua 脚本都要求所有 key 在同一个槽。解法是 hash tag:

bash
redis-cli -c -p 7000 -a '密码' mset '{user:1001}:name' tom '{user:1001}:age' 20
redis-cli -c -p 7000 -a '密码' mget '{user:1001}:name' '{user:1001}:age'
text
OK
1) "tom"
2) "20"

解释:花括号里的内容才是参与 CRC16 计算的 key,{user:1001} 让两个 key 落到同一个槽。

hash tag 用错会造出超级大槽

如果全站都用 {cache}:xxx,所有 key 会挤到同一个槽、同一台机器上,集群等于没做。hash tag 的粒度应该是"需要一起操作的 key",不是"业务前缀"。

其他 Cluster 限制:

  • 只有 db 0,SELECT 1 直接报错。
  • KEYS 只能扫本节点,要用 redis-cli --cluster call 或 --scan 逐节点。
  • FLUSHALL 也只清本节点。
  • PIPELINE 里的命令如果跨槽,会失败;按槽拆成多个 pipeline。

七、扩容:加一组主从 ​

新增机器 10.0.0.14(7006 主)、10.0.0.15(7007 从)。

1. 启动新节点 ​

按第二、三节建好配置和 unit,启动后确认 cluster_state:fail 且 cluster_known_nodes:1——它还没加入集群。

2. 加入集群 ​

bash
# 加主节点
redis-cli -a '密码' --cluster add-node 10.0.0.14:7006 10.0.0.11:7000

# 加从节点,指定要跟随哪个 master 的 node-id
redis-cli -a '密码' --cluster add-node 10.0.0.15:7007 10.0.0.11:7000 \
  --cluster-slave --cluster-master-id <新主node-id>

输出:

text
>>> Adding node 10.0.0.14:7006 to cluster 10.0.0.11:7000
>>> Performing Cluster Check (using node 10.0.0.11:7000)
[OK] All nodes agree about slots configuration.
>>> Send CLUSTER MEET to node 10.0.0.14:7006 to make it join the cluster.
[OK] New node added correctly.

新主此时 slots 0,不持有任何数据。

3. 迁移槽位 ​

bash
redis-cli -a '密码' --cluster reshard 10.0.0.11:7000 \
  --cluster-from <源node-id> \
  --cluster-to <新主node-id> \
  --cluster-slots 1000 \
  --cluster-yes

交互式版本会问:迁移多少个槽、从哪些节点迁、目标是谁。输出:

text
Moving slot 5461 from 10.0.0.11:7000 to 10.0.0.14:7006
Moving slot 5462 from 10.0.0.11:7000 to 10.0.0.14:7006
...
[OK] All 16384 slots covered.

迁移是同步逐个 key 搬的

1000 个槽如果包含几十万 key,会跑很久,期间源节点 CPU 和网络都上去了。可以分多次小批量迁(每次 200~300 槽),或放到低峰。迁移期间集群能正常读写。

4. 从节点跟上 ​

--cluster add-node 加进来的从节点会自动同步它的 master,不需要迁移槽位。

八、缩容 ​

顺序是先迁槽,再删节点:

bash
# 1. 把待删主节点的槽全部转给其他节点
redis-cli -a '密码' --cluster reshard 10.0.0.11:7000 \
  --cluster-from <待删node-id> \
  --cluster-to <接收node-id> \
  --cluster-slots 5461 --cluster-yes

# 2. 先删从节点
redis-cli -a '密码' --cluster del-node 10.0.0.15:7007 <从node-id>

# 3. 再删主节点
redis-cli -a '密码' --cluster del-node 10.0.0.14:7006 <主node-id>

主节点还有槽时 del-node 会失败

[ERR] Node ... is not empty. Either the node already knows other nodes...。必须先把槽迁空,确认 cluster nodes 里该节点是 slots 0。

九、故障转移 ​

停掉一个 master 试试:

bash
systemctl stop redis-cluster@7000
# 等 cluster-node-timeout 之后
redis-cli -p 7002 -a '密码' cluster nodes
text
3d41b7c0... 10.0.0.11:7000@17000 master,fail - 1761549700000 1761549688000 1 disconnected
...
10.0.0.11:7001@17001 slave 5a6b7c8d... 0 1761549703000 4 connected

它的 replica(7001)会升为主。日志里能看到:

text
* FAIL message received from 9c2e4f1a... about 3d41b7c0...
# Failover auth granted to 9c2e4f1a... for epoch 7
# Start of election delayed for 1200 milliseconds
# Cluster state changed: ok

失败判定的三个条件:

  1. 超过 cluster-node-timeout 收不到心跳。
  2. 该 master 至少有一个从库能连上(没有从库就不会自动切换,槽位永久 fail)。
  3. 从库通过 cluster-require-full-coverage 的约束(默认 yes,有槽不可用就整个集群拒绝服务)。

所有节点重启时有个坑:必须先起所有从库,再起主库,或者让它们都起来后再等自动切换。顺序错了可能触发多次无谓切换。

十、踩过的坑 ​

现象根因处置
cluster_state:fail,槽位没满有槽未分配--cluster fix 10.0.0.11:7000
建集群卡住 / 节点互相不认识总线端口 17000 没放通开放 port+10000
容器里节点广播了错误 IP没配 cluster-announce-ip显式配置并重启
CROSSSLOT多 key 不在同一槽用 hash tag,或拆成多次单 key 操作
MOVED 无限循环客户端把 ASK 当 MOVED 缓存了换支持的客户端,检查槽位表刷新逻辑
切主后短时间大量超时客户端槽位表还是旧的,重试风暴客户端配置刷新间隔 + 重试退避
一台 master 挂了整个集群 fail它没有从库 / cluster-require-full-coverage yes补齐从库,或权衡后改 no
迁移时业务报 TRYAGAIN槽正在迁移,key 不在源也不在目标客户端重试(幂等命令)
nodes-7000.conf 被手改后集群乱该文件是节点自维护状态停服务、删掉它、重新 --cluster create(会丢数据)