☰
Redis切片集群全解:16384个槽位背后的原理与运维实战
2026/10/23 8:00:42 网站建设 项目流程

1. 切片集群到底解决了什么问题

先聊一个我早期踩过的坑。某天线上一个 Redis 实例内存报警,我看了一眼监控,used_memory 已经逼近 30GB,这个实例还承担着大量读写。当时的第一反应是加内存,于是把机器从 32G 升到 64G,结果不到两个月又满了。后来复盘才发现,业务缓存 key 的增速远超预期,单机内存再翻一倍也只是把问题往后推。真正要命的是,这台机器 CPU 在高峰期也冲到 80% 以上,就算内存无限大,单线程的 Redis 处理能力也扛不住。这时候最合理的做法不是继续纵向扩容,而是把数据分散到多台机器上,让多台机器一起分摊读写压力——这就是切片集群的核心价值。

在 Redis 官方给出的高可用方案里,主从复制加哨兵解决的是“一个节点挂了业务还能继续”的问题,但它没有解决“单节点的数据和压力太大”的问题。切片集群正好补上这块短板:它把完整的 key 空间按照某种规则切分成多个分片,每个分片由一个独立的 Redis 实例负责,客户端根据 key 的哈希结果把请求路由到对应分片。每个分片的数据量变小了,单实例的 CPU 和内存压力也变小了,整体容量和处理能力可以水平扩展。

Redis 切片集群主要有三种落地方式:客户端分片、代理分片、官方 Redis Cluster。客户端分片就是在业务代码里自己算哈希,把 key 映射到不同 Redis 实例,这个方案最灵活但维护成本高,一旦节点变化,所有客户端都要改。代理分片比如 Twemproxy、Codis,是在客户端和 Redis 之间加一层代理,客户端无感知,但代理本身会成为瓶颈,也需要额外维护。官方 Redis Cluster 从 Redis 3.0 开始提供,把分片逻辑、节点通信、故障转移内置到 Redis 本身,是当前生产环境最主流的选型。

如果你刚接触这个问题,我建议直接聚焦官方 Redis Cluster。下面的内容全部围绕它展开,包括槽位分配原理、节点通信机制、数据迁移流程、集群部署步骤、常见故障排查,以及我在实际维护中积累的一些经验教训。文章会涉及一些配置项和命令,但重点会放在“为什么这样做”上,理解了原理,很多问题不用背命令也能自己排查。

2. 切片集群的核心设计:16384 个槽位

2.1 为什么是 16384 而不是更多

Redis Cluster 采用虚拟槽分区,整个 key 空间被划分为 16384 个哈希槽,每个 master 节点负责其中一部分槽位。写入一个 key 时,Redis 先对 key 计算 CRC16 校验和,然后对 16384 取模,得到这个 key 所属的槽位,再根据槽位分配关系定位到具体节点。

这个设计比一致性哈希更直观,运维上也更可控。一致性哈希需要处理虚拟节点、数据倾斜、节点增减时的哈希环调整,而槽位方案就是一张静态的表:每个节点管哪些槽,一目了然。槽位数量为什么选 16384,官方给出的解释是,CRC16 算法输出的值是 16 位,理论上范围是 0 到 65535,选择 16384 是权衡后的结果。

从实际角度理解,16384 这个数在集群规模不大时足够用,而且它带来了两个好处。第一,节点间心跳消息里用 bitmap 表示槽位分配信息,16384 个槽位只需要 2KB(16384 / 8 = 2048 字节),如果换成 65536 个槽位,bitmap 就要 8KB,心跳消息变大,网络开销成倍增加,节点数量多时会很明显。第二,槽位数量过大或者过小都不好:太小会导致数据分布粒度粗,某些热点 key 集中在一个槽里时难以打散;太大则会让元数据膨胀,迁移和同步也会变慢。对于绝大多数业务,几千个分片节点已经很少见,16384 个槽位绰绰有余。

2.2 槽位和数据如何映射

具体计算过程是这样的:客户端或者任意一个集群节点收到 key 后,先用哈希函数算出槽位,然后查询槽位和节点的对应关系。这个对应关系不是靠中心化存储,而是每个节点都保存一份完整的槽位映射表,同时通过 gossip 协议互相同步。

哈希计算有一个特殊规则需要记住:如果 key 中包含花括号{},那么 Redis 只对花括号内的部分做哈希,花括号外的内容不参与计算。这个设计叫 hash tag,目的是让某些 key 强制分配到同一个槽位,从而支持在同一个节点上执行 multi-key 操作。

举个例子:

key1 = {user:1001}:profile key2 = {user:1001}:orders

这两个 key 都只对user:1001做 CRC16,计算结果一定相同,所以它们一定会落在同一个槽位,也就一定在同一台节点上。这样你就可以放心使用MGET或者 Lua 脚本同时操作这两个 key,而不需要担心跨节点访问报错。

但 hash tag 也是一把双刃剑。如果你把大量业务 key 都打上同一个 tag,比如都用{user:1001},那这些 key 全挤在一个槽位里,数据倾斜会非常严重。我一个朋友曾经把所有用户的订单 key 都设计成{user:id}:order的形式,单看每个用户的数据确实落在一起,高性能没问题,但某个大用户的 key 数量非常多,直接导致那个槽位所在节点的内存和 CPU 飙高。后来只能通过手动迁移槽位暂时缓解,治标不治本。所以使用 hash tag 前,一定要评估单个 tag 下的 key 数量上限。

另外还有一个细节:计算槽位用的是 CRC16,不是 MurmurHash,也不是别的哈希函数。CRC16 在 Redis 里的实现是对 0x1021 多项式的一个查表算法,速度很快,分布也足够均匀。如果你自己实现客户端,一定要严格按照 Redis 官方文档中的 CRC16 实现来写,否则槽位算不对,路由就会出错。网上有不少客户端库已经实现了这套逻辑,不建议自己从头造轮子,除非你想研究协议。

3. 节点的通信与故障转移机制

3.1 心跳消息里到底传了什么

Redis Cluster 的节点之间是通过 gossip 协议通信的,没有中心节点。每个节点会定期向其他节点发送 PING 消息,也会主动 PONG 回应别人的 PING。这里的核心点是:心跳消息里不只是“我还活着”这个信息,还携带了本节点已知的其他节点状态、槽位分配信息、以及集群的 epoch 等元数据。

我最早以为集群通信很简单,直到有一次排查一个“节点状态不一致”的问题,才仔细研究了消息格式。每条 gossip 消息会带上发送节点认为的“集群全貌”:它知道哪些节点在线、哪些节点被标记为 FAIL、每个节点的槽位范围是什么。因为每个节点都是通过不断交换信息来逐步收敛出同一个视图,所以在一个节点刚刚发生故障时,不同节点对故障的感知可能有先后差异,这是正常的,最终会在几个心跳周期内达成一致。

心跳间隔由cluster-node-timeout控制。这个参数默认值是 15000 毫秒,即 15 秒。如果节点 A 在超过cluster-node-timeout时间内没有收到节点 B 的任何消息,A 就会认为 B 可能挂了,开始标记 PFAIL(possible fail,主观下线)。注意这里的关键词是“主观”,因为 A 的消息可能只是网络延迟或丢包,B 其实还活着。为了避免误判,A 会把这个判断通过 gossip 消息传播给其他节点,如果集群中超过一半的 master 节点都认为 B 已经 PFAIL,B 才会被标记为 FAIL(客观下线),此时才触发故障转移。

3.2 从节点晋升的完整流程

一个 master 节点被标记为 FAIL 后,它的从节点会自动发起选举,争取把自己提升为新的 master。这个选举过程类似于 Raft 算法的选举,但不是完全照搬,Redis Cluster 有自己的实现方式。

具体流程是:每个从节点会检查自己复制的 master 是否处于 FAIL 状态,如果是,它会先延迟一段时间(这个延迟时间和从节点自身的复制偏移量有关,复制越完整,延迟越短),然后向集群中所有 master 节点发送投票请求。收到请求的 master 会判断:这个从节点是否有资格参选,以及自己在这个选举周期内是否已经投过票。每个 master 在一个选举周期内只有一张票,所以最终得票超过半数的从节点胜出,成为新的 master。

这里有个常见误区:不是所有从节点都可以参选,只有数据足够新的从节点才有资格。如果从节点与旧 master 的复制延迟过大,它的数据可能不完整,直接被选举也没用,因为会丢很多数据。Redis Cluster 在选举时要求从节点的复制偏移量要满足一定条件,具体可以看源码逻辑,但运维层面你只需要知道:让从节点尽可能跟上主节点,是保障故障切换不丢数据的根本。

还有一个运维上的点:故障转移并不是秒级完成的。从发现故障到标记 FAIL,到从节点发起选举,再到选举成功并完成槽位接管,整个过程通常在 15 秒到 30 秒之间。如果业务不能接受这么长的中断,你需要考虑在客户端做本地缓存降级,或者提前在架构层面做多集群容灾。我见过不少团队上线了 Cluster 就以为高可用万无一失,结果故障发生时发现业务已经报错十几秒,才意识到“高可用”不等于“零感知”。

3.3 为什么要避免“脑裂”

脑裂是分布式系统老生常谈的问题。在 Redis Cluster 中,如果一个 master 节点网络异常,被其他节点标记为 FAIL,但它的进程还活着,照样接收客户端请求,而此时从节点已经升级为新的 master。如果旧 master 之后网络恢复,就会形成两个节点同时对外提供同一份槽位服务的情况,这就是脑裂。

为了防止脑裂导致数据不可恢复,Redis 提供了两个关键配置:min-replicas-to-write和min-replicas-max-lag。前者表示一个 master 至少要有多少个健康从节点才允许写入,后者表示从节点复制延迟的最大秒数。如果从节点数量不足或者延迟过大,master 会拒绝写入。

举个例子,你设置min-replicas-to-write 1、min-replicas-max-lag 10,意思是:master 在写入前会检查自己至少有一个从节点连接正常,且复制延迟不超过 10 秒。当网络分区发生时,旧 master 与所有从节点断开,它无法满足“至少有一个从节点”的条件,就会拒绝写入,这样就不会产生“旧 master 写入了一堆新数据,但新 master 没有这些数据,网络恢复后冲突无法解决”的问题。

这个配置需要根据业务容忍度来调整。如果业务可以接受少量数据丢失,你可以把min-replicas-to-write设为 0,让旧 master 继续写入,等恢复后再合并;如果业务对数据一致性要求很高,就设置成 1 或 2。我的建议是:默认至少设置为 1,尤其是金融、交易类业务,宁可短暂不可用,也不要让脑裂污染数据。

4. 一次完整的集群搭建实操

4.1 规划节点结构

搭建 Redis Cluster 前,先规划好节点角色和端口。以最简的 3 主 3 从为例:6 个 Redis 实例跑在同一台机器上只是为了演示,生产环境建议至少 6 台物理机或虚拟机,主从尽量不要部署在同一台物理服务器上,否则物理机宕机,主从一起挂,集群高可用就名存实亡。

端口我习惯用 7000 到 7005,其中 7000、7001、7002 为主节点端口,7003、7004、7005 为对应从节点端口。注意 Cluster 模式还有一个总线端口,默认是 Redis 端口加 10000,比如 7000 的总线端口是 17000。这个端口用于节点间 gossip 通信,防火墙一定要放行。

每台机器上准备一个 redis.conf,核心配置如下:

port 7000 daemonize yes protected-mode no dir /data/redis-cluster/7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes

最关键的是cluster-enabled yes,这代表以 Cluster 模式启动。cluster-config-file是节点自己保存集群元数据的文件,不需要手动创建,启动后会自动生成。如果这个文件丢失,节点会认为自己不在集群中,需要重新加入。所以我建议把节点数据目录和配置文件分离,避免误删。

4.2 启动实例并创建集群

6 个实例配置好之后,分别启动:

redis-server /path/to/redis-7000.conf redis-server /path/to/redis-7001.conf redis-server /path/to/redis-7002.conf redis-server /path/to/redis-7003.conf redis-server /path/to/redis-7004.conf redis-server /path/to/redis-7005.conf

启动后可以先验证一下进程:

redis-cli -p 7000 ping

返回 PONG 说明正常。接下来使用 redis-cli 的集群管理命令创建集群:

redis-cli --cluster create 192.168.1.10:7000 192.168.1.11:7001 192.168.1.12:7002 \ --cluster-replicas 1

这里只填写了 3 个主节点地址,--cluster-replicas 1表示每个主节点配 1 个从节点。但注意,这个命令只靠 3 个地址没法自动分配从节点,它要求你同时提供 6 个节点地址,然后根据地址顺序自动分配主从关系。正确写法是:

redis-cli --cluster create 192.168.1.10:7000 192.168.1.11:7001 192.168.1.12:7002 \ 192.168.1.10:7003 192.168.1.11:7004 192.168.1.12:7005 \ --cluster-replicas 1

执行后,redis-cli 会展示一个分配方案,比如主节点 7000 的从节点是 7004,主节点 7001 的从节点是 7005,主节点 7002 的从节点是 7003,并询问你确认。输入 yes 后,集群开始初始化,每个主节点会分到 5461 个槽位(16384 / 3 ≈ 5461),从节点开始同步主节点数据。

这个命令执行完毕,你就拥有了一个 3 主 3 从的切片集群。验证集群状态:

redis-cli -p 7000 cluster info redis-cli -p 7000 cluster nodes

cluster info里看cluster_state:ok,代表集群健康。cluster nodes会列出所有节点的 ID、角色、槽位范围,以及主从对应关系。

4.3 槽位分配不均怎么办

有时候创建集群时并没有均匀分配槽位,或者你想手动画分槽位,可以使用reshard命令。比如把 100 个槽位从节点 A 迁移到节点 B:

redis-cli -p 7000 --cluster reshard 192.168.1.10:7000

这个命令是交互式的,它会问你:要迁移多少个槽位?接收槽位的节点 ID 是哪个?要从哪些节点迁出槽位?如果不想交互,也可以直接用--cluster-from和--cluster-to指定:

redis-cli -p 7000 --cluster reshard 192.168.1.10:7000 \ --cluster-from <源节点ID> --cluster-to <目标节点ID> \ --cluster-slots 100 --cluster-yes

执行迁移时,Redis 会先把源节点上对应槽位的 key 逐个复制到目标节点,复制完成后在源节点删除这些 key,再把槽位的归属关系切换为目标节点。这个过程对客户端是缓慢感知的,因为迁移期间访问这些 key 时,会返回 ASK 重定向,客户端需要跟随重定向到目标节点访问。

这里有个实战经验:迁移槽位时,如果源节点内存很大,迁移过程中的 key 复制会占用大量网络带宽和 CPU,建议在业务低峰期操作,并且可以控制迁移速率。Redis 6.2 之后支持了cluster-allow-replica-migration等相关参数,但最直接的方式还是分批手动迁移,不要一次迁几千个槽位。

5. 客户端如何与切片集群交互

5.1 MOVED 与 ASK 重定向

Redis Cluster 的客户端要能正确处理两种重定向:MOVED 和 ASK。

MOVED 发生在客户端请求的 key 所在槽位不在当前节点管辖范围内。比如你连接 7000 节点执行GET foo,7000 节点算完槽位后发现自己不管这个槽位,就返回错误:

(error) MOVED 1234 192.168.1.12:7002

意思是:你要找的槽位 1234 在 192.168.1.12:7002 上,请去那里访问。一个聪明的客户端会缓存这个槽位信息,下次直接访问正确节点,不会每次都走一次重定向。

ASK 发生在槽位正在迁移的过程中。当源节点准备把槽位 1234 迁移到目标节点时,如果客户端访问源节点上属于这个槽位的 key,源节点发现这个 key 已经被迁移走了,就返回 ASK 重定向。和 MOVED 的区别是,ASK 只是针对当前 key 的一次性重定向,客户端不应该缓存“槽位 1234 在目标节点上”这个信息,因为迁移还没完成,后续 key 可能还在源节点上。

我早期用命令行工具 redis-cli 测试时,因为 redis-cli 默认不会自动处理 MOVED,经常以为自己连错了节点。其实redis-cli -c开启集群模式后就会自动跟随重定向:

redis-cli -c -p 7000

使用集群模式时,执行跨槽位多 key 命令才会报错,因为它要求所有 key 必须哈希到同一个槽位。

5.2 客户端库选型的几个坑

生产环境我强烈建议使用官方推荐的客户端库,比如 Java 的 Lettuce 或 JedisCluster、Go 的 go-redis。不同客户端对集群的适配程度差异很大,下面说几个关键点。

第一,连接池策略。有些客户端默认每个节点一个连接池,有些默认共享连接池再通过路由选择节点。如果你的业务热点 key 非常集中,共享连接池可能导致单个节点连接数过高,而其他节点连接空闲。我踩过一次 go-redis 的坑:默认的 ClusterClient 在每次命令时都会计算节点,热点 key 全部打到同一个节点,连接池上限设得不够,导致排队阻塞。后来调整了连接池大小,同时用 hash tag 把热点打散,问题才缓解。

第二,重定向处理。合格客户端必须在收到 MOVED 后更新本地槽位缓存,收到 ASK 后重新路由当前命令。如果你自己写客户端,一定要实现这两套逻辑,特别是 ASK 的处理,很多初级实现会把 ASK 当成 MOVED 处理,导致迁移期间数据访问异常。

第三,跨槽位事务与 Lua。官方 Cluster 不支持跨槽位的 multi-key 操作,除非所有 key 落在同一个槽位。Lua 脚本也一样,脚本中涉及的 key 必须属于同一节点,否则会报 CROSSSLOT 错误。这是设计限制,不是 bug,业务建模阶段就要避免对多个用户的数据做聚合操作。

5.3 直连模式与代理模式的选择

现在的 Redis 客户端大体有两种集群接入模式:直连模式和代理模式。直连模式下客户端直接与每个节点通信,性能最高,但客户端要维护槽位映射,节点故障和迁移时也要处理重定向。代理模式例如 Redis Enterprise 的 proxy,或者第三方工具,客户端连接代理,代理转发请求,客户端无需感知集群内部变化,但多一层网络转发,延迟略高。

如果业务读多写少且对延迟极其敏感,我建议直连。如果团队里有很多业务线共用一套集群,又不想每个业务都处理集群细节,代理模式更省心。Redis 6 以后官方对代理模式没有原生支持,但 codis 等旧方案依然有存量使用。我的态度是:新项目一律官方 Cluster 直连,代理只在历史包袱大的场景使用。

6. 扩容、缩容与数据迁移实战

6.1 新增主节点并迁移槽位

假设现在集群 3 个主节点内存使用率都到了 70%,需要扩容到 4 个主节点。步骤分两步:加入新节点,迁移部分槽位过去。

第一步,准备一台新机器,启动一个 Cluster 模式的 Redis 实例,端口 7006。然后用以下命令把它加入集群:

redis-cli -p 7000 --cluster add-node 192.168.1.13:7006 192.168.1.10:7000

第一个地址是新节点,第二个地址是集群中任意一个现有节点。执行后,新节点会以 master 身份加入集群,但此时它不拥有任何槽位,需要手动迁移。

第二步,查看新节点 ID:

redis-cli -p 7000 cluster nodes | grep 7006

拿到 ID 后,执行 reshard,把一部分槽位从旧的某个或某几个主节点迁到新节点。目标槽位数量可以根据新机器内存规划,比如现在总槽位 16384,要 4 个主节点均分,每个节点应该有 4096 个槽位,那么新节点需要接收大概 4096 个槽位,源节点可以选旧的 3 个主节点,每个迁出约 1365 个。

迁移过程耗时取决于迁移的 key 数量和单个 key 大小。如果一个大 key 是几十 MB,迁移时会整块复制,阻塞时间较长。我建议在迁移前用redis-cli --cluster rebalance自动平衡,它比手动 reshard 更智能,但要注意 rebalance 可能会在多个节点之间同时产生迁移流量,最好在低峰期执行。

6.2 添加从节点增强可用性

只加主节点不加从节点,集群在数据高可用上仍然有风险:新主节点一旦宕机,它负责的槽位就没有从节点接管,客户端访问这些槽位时会直接失败。所以每加一个主节点,至少要加一个从节点。

添加从节点的方法:

redis-cli -p 7000 --cluster add-node 192.168.1.13:7007 192.168.1.10:7000 --cluster-slave --cluster-master-id <新主节点ID>

这个命令把 7007 节点作为指定主节点的从节点加入集群。加入后从节点开始全量同步主节点数据,同步期间不要急于对外提供服务,等同步完成再观察集群状态。

从节点数量也不是越多越好。每个从节点都在复制主节点的全量数据,多一个从节点就多一份内存和带宽消耗。一般 1 到 2 个从节点足够,再加更多只是浪费资源。如果担心跨机房容灾,可以优先考虑主从分布在不同机房或不同可用区,而不是在一个机房堆多个从节点。

6.3 缩容时千万注意的顺序

缩容比扩容更容易出问题。我见过有人直接停掉一个 master 节点,结果它负责的槽位变成无人管辖,集群返回 CLUSTERDOWN,所有相关 key 不可用。正确做法是:先把该节点负责的槽位全部迁移出去,确认槽位为 0,再从集群中移除节点。

移除节点的命令:

redis-cli -p 7000 --cluster del-node 192.168.1.10:7000 <节点ID>

这个命令会检查节点是否还有槽位,如果有,会拒绝移除。所以顺序一定是:先 reshard 迁走所有槽位,再执行 del-node。如果是从节点,没有槽位,可以直接 del-node,但最好先确认没有复制偏移量落后过多,否则数据丢失会影响主节点的故障切换能力。

缩容还有一个小坑:如果集群里有多个 master 节点共享某些备份数据或者哨兵监控,缩容后要同步更新监控平台、客户端配置、DNS 解析,避免健康检查误报。

7. 生产环境常见问题排查实录

7.1 集群状态变成 FAIL 的排查思路

当cluster_state不是 ok 而是 fail 时,最常见的原因是:某个 master 节点及其所有从节点同时不可用,导致有一部分槽位没有可用节点。此时整个集群会拒绝所有读写请求,返回 CLUSTDOWN 错误。这个行为是故意设计的,目的是避免读到不一致的数据,但很多业务方在第一次碰到时会惊出一身冷汗。

排查步骤我一般按以下顺序:

  1. 先看cluster nodes,找出哪个节点是 FAIL 或 PFAIL。
  2. 登录对应机器,看 Redis 日志和系统日志,确认是进程崩溃、宕机还是网络分区。
  3. 如果只是网络抖动导致误判,通常不需要人工干预,等cluster-node-timeout过了之后节点会重新加入,状态自动恢复。
  4. 如果是进程挂了,尽快启动进程,让它重新加入集群。如果是从节点挂了,也要尽快恢复,否则主节点一旦再挂,就会触发 FAIL 状态。

如果是多机房部署,还要重点检查机房之间的网络质量。我曾经遇到一个集群频繁出现节点 PFAIL 然后又自动恢复的情况,最后发现是交换机端口丢包率偏高,导致心跳消息丢失严重。这类问题单纯看 Redis 日志很难定位,需要结合网络监控和心跳超时统计来判断。

7.2 数据倾斜和热点问题的应对

切片集群虽然让整体数据分布更均匀,但热点 key 依然存在。比如某活动的爆款商品、某个大 V 的粉丝列表,访问量可能是普通 key 的上百倍。这些 key 无论落在哪个槽位,都会让对应节点压力巨大。

处理手段通常有三种:

第一,热点 key 加随机后缀。比如原本 key 是product:123,你可以拆成product:123:0、product:123:1到product:123:9这 10 个 key,分别存到不同槽位,读取时随机取一个。这个方案需要在业务代码里做逻辑拆分,适合 value 可以冗余的场景。

第二,本地缓存兜底。在应用服务器上加一层热点 key 的本地缓存,比如 Caffeine 或 Go 的 freecache,过期时间设置足够短,几秒钟到十几秒。对于热点极高的 key,这个方案能显著降低 Redis 压力。

第三,hash tag 慎用。有些团队为了让某些 key 能批量操作,把所有相关信息都打上同一个 tag,结果反而制造了热点。如果确实需要 hash tag,尽量把 tag 的粒度控制在单个用户或单个业务域内,并确保每个 tag 下的 key 总量不会过大。

7.3 慢查询和阻塞操作的影响范围

Cluster 模式下,一个节点的慢查询不会直接拖垮其他节点,但如果慢查询发生在 master 上,它的从节点复制也会受到影响。比如你在 master 上执行KEYS *,这个命令会阻塞 Redis 单线程事件循环相当长时间,期间所有命令都排队。从节点同样在执行复制的命令流,主节点阻塞会直接导致复制延迟增加,一旦延迟超过cluster-node-timeout,可能触发从节点发起选举,造成不必要的故障切换。

所以在我维护的集群里,KEYS命令是禁止上生产的,一律用SCAN代替。SMEMBERS如果集合很大,也应改成SSCAN分批取。HGETALL同理,大 hash 要警惕。还有一种隐蔽的阻塞操作是DEL一个大 key,比如一个 value 有几百万个元素的集合,删除时会连续释放内存,导致阻塞。这时候建议用UNLINK,它就是为这件事设计的异步删除命令,Redis 会先把 key 从命名空间摘除,再在后台线程逐步释放内存。

7.4 集群节点时间不同步的问题

很多人忽略这一点:集群节点之间要求时钟尽量同步,否则心跳超时判断和选举投票会出问题。如果某个节点系统时间和别的节点差了几十秒,它的 PING 消息携带的时间戳会让其他节点误判。特别是故障转移时,从节点等待延迟和投票窗口依赖于时间,时钟偏移严重时可能导致选举超时或重复投票。

生产环境必须配置 NTP 或 chrony 时间同步。这不是可选项,是必选项。我见过一个集群因为某台云主机时间漂移了 5 分钟,每次主节点故障切换都会失败,最后排查半天才发现是时间问题。

7.5 常见问题速查表

现象可能原因解决思路
写入报 CLUSTERDOWN某槽位无可用 master恢复故障节点或迁移槽位
报 MOVED 错误客户端未自动跟随重定向开启客户端集群模式或更换客户端
报 ASK 错误槽位迁移中客户端应临时重定向,勿缓存
执行多 key 命令报 CROSSSLOTkey 不在同一槽位使用 hash tag 或拆分命令
某节点内存明显偏高hash tag 使用不当调整 tag 粒度或单独拆分
故障切换后数据丢失从节点复制延迟过大检查主从复制,限制写入条件
集群状态反复 PFAIL网络丢包或时间不同步检查网络质量和系统时钟
主从切换后旧主自动重新加入网络分区恢复确认脑裂配置防丢数据

8. 我的一些运维习惯建议

最后聊几个实操层面的习惯,覆盖你真正把集群跑起来之后会遇到的日常场景。

第一个习惯:把监控做细。切片集群的监控不能只看used_memory和cpu,要按节点维度看。每个主节点的内存、命中率、慢查询数、复制延迟、连接数,都建议独立监控和告警。因为集群的瓶颈往往不是整个集群,而是某个特定节点。我遇到过的情况是:整体集群 CPU 不到 10%,但某个主节点已经接近 100%,因为热点 key 全部打在那里。如果只做了集群聚合监控,这个问题会被掩盖。

第二个习惯:定期演练故障切换。不要等到真的宕机了才看从节点能不能晋升。找一台低峰期机器,手动redis-cli -p 7000 debug sleep 30模拟主节点阻塞,或者直接kill掉主节点进程,观察集群多久恢复、客户端是否报错、数据有没有丢失。演练一次,至少能发现监控盲区和客户端配置问题。

第三个习惯:内存碎片和过期 key 的治理。Cluster 模式下每个节点是独立的 Redis 实例,碎片率、过期 key 扫描也都是各节点独立的。你可以通过redis-cli --cluster info查看每个节点的内存状况,定期用scan配合OBJECT idle找出长期未访问的 key,和业务方确认是否可清理。不要指望一个节点清理内存,其他节点会自动均衡,Redis Cluster 没有自动均衡数据的后台任务。

第四个习惯:配置变更要滚动执行。Cluster 节点多,如果改maxmemory-policy、cluster-node-timeout这类参数,逐个节点修改并重启,避免一次性把整个集群重启。重启一个节点后,等集群状态稳定,再重启下一个。千万别同时重启多个主节点,否则可能触发多槽位同时无主,集群直接进入 DOWN 状态。

第五个习惯:客户端重试机制必须有。节点故障切换期间,客户端可能连到一个正在选举的节点,或者收到暂时性的连接错误。成熟的客户端库都有重试策略,但要确认重试不会把雪球滚大。建议设置合理的重试次数和退避时间,比如最多重试 3 次,退避 100ms 起步。同时,重试时不要盲目把请求发给所有节点,先依赖槽位缓存,让 MOVED/ASK 逻辑来引导路由。

我实际跑切片集群这几年,最大的感受是:Redis Cluster 本身是一个相当成熟、稳定的组件,大部分线上事故并不是组件 bug,而是使用方对槽位迁移、故障切换、热 key 分布理解不够,或者操作顺序错误。把原理吃透,把演练常态化,这个架构可以支撑大多数中大规模业务。

如果你正准备上切片集群,先从 3 主 3 从开始,不要一上来就搞几十个节点。先把扩容、迁移、故障切换跑一遍,确认客户端行为符合预期,再逐步扩大规模。这样踩坑成本最低,也能让你对集群的脾气摸得更清楚。

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

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

立即咨询