Redis Sentinel可以让单个主库在故障后自动切换,但所有写入和全部键空间仍然集中在一个主节点。当数据量、网络带宽或写吞吐超过单机上限时,仅增加副本无法继续扩展写能力。

Redis Cluster 同时引入两件事:把键空间分片到多个主节点,并为每个主节点配置副本。它解决的是水平扩展和分片高可用问题,代价是客户端、数据模型和运维过程都变得更复杂。

Cluster 与 Sentinel 的根本区别

Sentinel 维护的是一个逻辑主库:任何时刻只有一个节点承载全部写入。Cluster 则有多个主节点,每个主节点只负责一部分键。

能力SentinelRedis Cluster
写主节点数量1 个多个
数据分片不支持支持
故障转移支持支持,每个分片独立主从切换
客户端要求支持 Sentinel 服务发现支持 Cluster 路由和重定向
多键限制普通 Redis 规则相关键通常必须位于同一槽位

如果数据和写流量仍适合单机,Sentinel 往往更简单。Cluster 不应该因为“节点更多”而被默认选择。

为什么是 16384 个哈希槽

Redis Cluster 不直接把某个键永久绑定到某台服务器,而是增加了一层固定数量的哈希槽:

key ── CRC16(key) mod 16384 ──> slot ──> 当前负责该槽位的主节点

全部键空间被划分为 016383,总计 16384 个槽位。每个主节点负责若干槽位,例如:

Master-A:0     - 5460
Master-B:5461  - 10922
Master-C:10923 - 16383

固定的是“键到槽位”的算法,变化的是“槽位由哪个节点负责”。扩容时只需要迁移一部分槽位及其键,而不需要改变所有键的哈希规则。

16384 个槽位也不是 16384 个独立进程或数据文件。槽位只是路由和管理单位,节点仍然使用普通 Redis 数据结构保存键值。

客户端如何找到正确节点

Cluster 没有代理层。客户端可以先连接任意节点,但命令最终必须到达负责目标槽位的主节点。

Cluster 感知客户端通常会缓存一份“槽位到节点”的映射。第一次请求或拓扑变化时,Redis 通过重定向响应帮助客户端修正路由。

MOVED:稳定归属已经改变

当客户端访问了错误节点,而目标槽位已经明确属于另一个节点时,Redis 返回:

MOVED 12182 192.168.10.12:7000

这表示客户端应把请求发送到新地址,并更新本地槽位缓存。redis-cli -c 会自动跟随这种重定向;普通 redis-cli 只会显示错误。

ASK:槽位正在迁移

槽位迁移期间,一部分键可能已经到达新节点,另一部分仍在旧节点。此时旧节点可能返回:

ASK 12182 192.168.10.12:7000

ASK 是一次性指引。客户端应先向目标节点发送 ASKING,再执行当前命令,但不应立即永久修改整个槽位缓存。迁移完成后,稳定重定向才会变成 MOVED

两者的差异可以概括为:MOVED 表示归属已改变,ASK 表示迁移中的临时访问路径。

哈希标签解决什么

默认情况下,键名的全部内容参与槽位计算。多个键落在不同槽位时,Cluster 无法在单个节点上原子执行涉及这些键的事务、Lua 脚本或多键命令。

哈希标签允许只使用键名中第一组有效花括号内容计算槽位:

order:{1001}:header
order:{1001}:items

两个键都使用 1001 计算槽位,因此会落到同一个节点。哈希标签应围绕确实需要共同操作的数据设计,而不是把所有键都放进同一个标签;后者会让流量重新集中到一个分片,失去水平扩展价值。

节点之间怎样交换状态

每个 Cluster 节点除了客户端端口,还需要一个 Cluster Bus 端口,默认是数据端口加 10000。节点通过二进制集群总线传播:

  • 节点存活和疑似故障信息。
  • 槽位归属及配置纪元。
  • 主从关系和故障转移状态。
  • 发布订阅等集群内部消息。

节点周期性交换 PINGPONG 和 Gossip 信息。每条 Gossip 消息会携带发送者已知的部分节点状态,因此拓扑变化可以逐渐扩散到整个集群。

客户端端口可达不代表集群一定健康。如果 Bus 端口被防火墙阻断,客户端可能还能连接单个 Redis,但节点之间无法可靠完成故障判断和配置传播。

从疑似故障到确认故障

Cluster 的故障判断也分为本地观察和集体确认:

  1. 某节点长时间无法联系另一个节点,会在本地把对方标记为 PFAIL
  2. 这些疑似故障信息通过 Gossip 传播。
  3. 负责槽位的主节点中有多数认为目标主节点不可达时,目标被标记为 FAIL
  4. 故障主节点的副本发起选举,争取其他主节点授权。
  5. 获得多数派支持的副本晋升,并接管原主节点的槽位。

多数派按“有投票权的主节点”计算,不按容器总数或副本数量计算。副本能够保存数据和参与晋升,但故障授权依赖主节点投票域。

3 主 3 从能承受什么故障

一个合理分布的 3 主 3 从拓扑可以让每个主节点的副本位于另一台物理机:

node-1               node-2               node-3
Master-A             Master-B             Master-C
Replica-B            Replica-C            Replica-A

任意一台物理机离线时,另外两台仍保留三个分片的数据副本,并且三个主节点投票域通常还能形成多数派完成晋升。

但“3 主 3 从”不等于可以任意失去三个节点:

  • 如果一个主节点及其唯一副本同时丢失,对应槽位就没有可服务的数据副本。
  • 如果剩余主节点无法形成多数派,集群不能安全授权故障转移。
  • 如果主从被错误地放在同一台物理机,整机故障会同时带走一个分片的两份数据。

节点数量只是基础,主从对应关系和故障域分布才决定实际可用性。

Cluster 仍然是异步复制

每个 Cluster 主节点与副本之间仍使用 Redis 主从复制。主节点确认写入时,副本可能尚未收到复制流。

网络分区时,旧主节点可能在一小段时间内继续接受客户端写入,随后因无法联系多数派而停止服务。如果多数派一侧已经提升新主,那么旧主尚未复制的写入可能在重新加入时被丢弃。

Cluster 优先提供可用性和水平扩展,不提供跨分片强一致事务,也不能保证故障时零数据丢失。

扩容不是增加容器就结束

向集群添加一个空主节点后,它还没有负责任何槽位,也就不会自然承担业务数据。扩容通常包含:

  1. 新节点加入集群。
  2. 为新主节点安排副本。
  3. 从现有主节点迁移一部分槽位。
  4. 迁移槽位内的键,并观察 ASKMOVED 的状态变化。
  5. 验证槽位完整性和负载分布。

缩容则需要先迁走目标节点负责的全部槽位,再把节点安全移出集群。普通开源 Redis Cluster 不会因为新增节点就自动把槽位均匀重排。

三服务器 3 主 3 从最小部署

下面在三台 Linux 服务器上各运行两个 Redis 实例:

服务器地址Redis 端口Bus 端口
node-1192.168.10.11700070011700017001
node-2192.168.10.12700070011700017001
node-3192.168.10.13700070011700017001

每个实例都必须有独立的 data/nodes.conf。以 node-1 的 7000/redis.conf 为例:

bind 0.0.0.0
protected-mode yes
port 7000

requirepass RedisBlog_ChangeMe_2026!
masterauth RedisBlog_ChangeMe_2026!
appendonly yes
appendfsync everysec
dir /data

cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.10.11
cluster-announce-port 7000
cluster-announce-bus-port 17000

daemonize no

7001/redis.conf 把端口改为 7001/17001。node-2 和 node-3 使用相同结构,但 cluster-announce-ip 分别改为自己的服务器地址。

三台服务器使用相同的 compose.yml

services:
  redis-7000:
    image: redis:8.8.0
    container_name: redis-cluster-7000
    restart: unless-stopped
    network_mode: host
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./7000/redis.conf:/usr/local/etc/redis/redis.conf:ro
      - ./7000/data:/data

  redis-7001:
    image: redis:8.8.0
    container_name: redis-cluster-7001
    restart: unless-stopped
    network_mode: host
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./7001/redis.conf:/usr/local/etc/redis/redis.conf:ro
      - ./7001/data:/data

在三台服务器启动节点:

docker compose up -d

六个端点全部可达后,只执行一次建群命令:

export REDISCLI_AUTH='RedisBlog_ChangeMe_2026!'

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7000 \
  redis-cli --cluster create \
    192.168.10.11:7000 192.168.10.12:7000 192.168.10.13:7000 \
    192.168.10.11:7001 192.168.10.12:7001 192.168.10.13:7001 \
    --cluster-replicas 1 --cluster-yes

redis-cli 会尝试避免把主节点和副本放在同一 IP,但建群后仍必须检查实际主从关系,不能只相信创建顺序。

验证槽位与客户端路由

查看集群概要:

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7000 \
  redis-cli -p 7000 CLUSTER INFO

关键结果应为:

cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_known_nodes:6
cluster_size:3

再检查节点角色和槽位覆盖:

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7000 \
  redis-cli -p 7000 CLUSTER NODES

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7000 \
  redis-cli --cluster check 192.168.10.11:7000

验收时确认有 3 个主节点、3 个副本、16384 个槽位全部覆盖,并且每组主从位于不同物理机。

使用 -c 验证客户端重定向:

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7000 \
  redis-cli -c -p 7000 SET article:cluster ok

docker exec -e REDISCLI_AUTH="$REDISCLI_AUTH" redis-cluster-7001 \
  redis-cli -c -p 7001 GET article:cluster

停止某个已确认是主节点的实例后,观察它的跨主机副本是否晋升、槽位是否仍完整。故障实验必须依据 CLUSTER NODES 的实际关系选择节点,不能仅凭端口猜测角色。

什么时候选择 Cluster

适合选择 Redis Cluster 的情况:

  • 数据量无法稳定放入一台 Redis。
  • 写吞吐或网络带宽需要多个主节点共同承担。
  • 客户端支持 Cluster 协议,数据模型能够适应槽位限制。
  • 团队具备管理槽位迁移、跨故障域副本和扩缩容的能力。

如果容量仍在单机范围,只需要自动主从切换,Redis Sentinel通常更简单。如果还没有建立复制、偏移量和异步一致性的基础认识,先阅读 Redis 主从复制:从全量同步到人工故障切换

参考资料