说到 Redis 7 集群搭建,我估计不少朋友已经踩过一轮坑了。网上教程确实多,但要么停留在单个实例的伪集群,要么只贴命令不讲为什么,真到自己动手把多机环境拉起来,还是会卡在节点握手、槽位分配、故障转移这些细节点上。这篇文章不是把官方文档搬过来,而是按我实际搭建的完整流程走一遍,从环境准备、编译安装、配置项逐条拆解,到集群初始化、数据分片原理,再到扩容缩容、常见故障排查,尽量把每一步背后的逻辑说清楚,方便你照着操作,也能在出了问题时自己定位。
1. 搭建前的关键决策:到底选哪种集群方案
1.1 主从复制、哨兵和 Cluster 三者的本质区别
很多人在搭集群之前,其实并没有想清楚自己到底需要什么。主从复制只是把数据同步到多个节点,解决的是读压力和单点故障,但它没有自动故障转移,主节点挂了需要人工介入。Redis Sentinel 在主从基础上加了监控和自动切换,能保证高可用,但它存的数据量依然受单机内存上限约束,写能力也只有一个主节点能扛。而 Redis Cluster 从一开始就是为“水平扩展”设计的,它把 16384 个哈希槽分散到多个主节点上,每个主节点只负责一部分数据,配合副本节点实现高可用,容量和写性能都能横向扩展。
如果你只是需要读写分离和高可用,主从加哨兵就够用;如果你对未来数据量心里没底,或者已经感觉到单机内存成为瓶颈,那直接上 Cluster 是对的。说实话,Cluster 的运维复杂度比哨兵高不少,节点间通信、槽位迁移、重定向机制都需要额外理解和维护,但它换来的是真正的横向扩容能力。当年我在生产环境从哨兵模式迁移到 Cluster,就是因为业务增长太快,单机 64G 内存完全扛不住,这个迁移过程后面你也迟早会遇到。
1.2 节点规划与端口分配:这些细节决定了你能不能少走弯路
规划集群时,最基础但也最容易被忽略的是端口规划。Redis Cluster 里每个节点除了对外服务的业务端口(比如 7000),还会自动占用一个集群总线端口,也就是端口加 10000 这个固定偏移量。也就是说,如果对外端口是 7000,那么节点还会监听 17000 用于节点间的 gossip 通信、故障检测和配置传播。这个端口很容易在配置防火墙安全组时被漏掉,一旦漏掉,节点之间就会一直处于握手失败或 PFAIL 状态,集群看起来好像建成了,实际上根本不稳定。
我习惯的规划方式是这样的:假设两台物理机,每台起三个实例,对外端口用 7000 到 7002 和 7003 到 7005,两台机器的 IP 分别记为 192.168.1.101 和 192.168.1.102。每个实例有自己的独立数据目录、日志目录和配置文件,好处是后面单独扩容、替换节点时互不影响。从节点尽量分布在不同的物理机上,比如主节点 7000 在机器 A 上,它的从节点 7003 放在机器 B 上,这样即使整台机器断电,数据也不丢,服务也能自动切换。具体的角色分配和目录我会在后面章节里给出一张表,方便你直接对照着抄。
2. 环境准备与 Redis 7 编译安装
2.1 系统依赖与内核参数调整
这里以常见的 RHEL/CentOS 系系统为例,其实 Debian/Ubuntu 的逻辑也差不多。先把编译工具链装上,Redis 7 编译需要 gcc、make,如果你的系统最小化安装过,大概率缺这两个。另外如果你要用到 TLS 特性,还需要 openssl-devel。命令很简单,但建议一次装全,避免编译到一半缺头文件又重新来。
yum install -y gcc gcc-c++ make装完编译器之后,我强烈建议调整两个内核参数,这一步很多人会忽略。第一个是vm.overcommit_memory,Redis 在生成 RDB 快照或执行 BGSAVE 时会 fork 子进程,即使 Redis 自己的内存使用量没超过物理内存,内核也有可能判定内存申请失败而拒绝 fork。你可以先直接执行下面三条命令来临时调试,确认没问题后再写入/etc/sysctl.conf和开机自启脚本里。
sysctl -w vm.overcommit_memory=1 echo never > /sys/kernel/mm/transparent_hugepage/enabled sysctl -w net.core.somaxconn=1024vm.overcommit_memory=1表示内核不检查内存申请是否真的够用,这能很大概率避免 fork 失败的问题。transparent_hugepage也就是透明大页,它对 Redis 这种内存访问密集型的应用非常不友好,关掉可以减少延迟抖动。net.core.somaxconn则是提高 TCP 连接队列上限,应对高并发连接时出现的 backlog 不足警告。这些参数不调整不会导致集群搭不起来,但运行一段时间后各种诡异的性能问题就会冒出来。
2.2 下载编译安装 Redis 7
接下来下载 Redis 7 的稳定版本。建议从官网或 GitHub 的 releases 页面获取,我这里以 7.2.5 为例。下载后先确认一下 tarball 的完整性,可以下载对应的.sha256文件或使用sha256sum自己核对,避免拿到损坏的包浪费时间。
wget https://download.redis.io/releases/redis-7.2.5.tar.gz tar xzf redis-7.2.5.tar.gz cd redis-7.2.5 make -j$(nproc) make install PREFIX=/usr/local/redis编译时开-j$(nproc)能明显缩短时间,四核机器实测大概一分钟左右就能编完。编译过程中如果看到 jemalloc、openssl 相关的输出,是 Redis 在编译内置的依赖库,属于正常现象。安装到/usr/local/redis以后,可执行文件会出现在/usr/local/redis/bin目录下,里面至少会有redis-server、redis-cli、redis-check-aof、redis-check-rdb、redis-sentinel等工具。
顺便说一句,如果你需要在 Windows 环境下解压 tar 包后传到 Linux 再解压,遇到中文文件名乱码,一般不是 Redis 的问题,而是 Windows 上的压缩工具没有按 UTF-8 编码打包,用系统的 tar 重新打包或者在 Linux 下解压就能避免。这和 Redis 安装本身关系不大,但很多初学者会把时间浪费在这类环境问题上,留意一下能省不少事。
3. 集群配置文件的逐项要点
3.1 目录结构准备好,后续维护少操心
我个人的习惯是先把目录结构准备好,再动手改配置。这里假设我的安装路径为/usr/local/redis,数据目录统一放/data/redis,每个实例一个子目录,日志也放在各自的目录里。创建一个集群需要六个实例,目录规划如下:
| 实例端口 | 对外端口 | 集群总线端口 | 数据目录 | 日志文件 | 物理机 |
|---|---|---|---|---|---|
| 7000 | 7000 | 17000 | /data/redis/7000 | /data/redis/7000/redis.log | 192.168.1.101 |
| 7001 | 7001 | 17001 | /data/redis/7001 | /data/redis/7001/redis.log | 192.168.1.101 |
| 7002 | 7002 | 17002 | /data/redis/7002 | /data/redis/7002/redis.log | 192.168.1.101 |
| 7003 | 7003 | 17003 | /data/redis/7003 | /data/redis/7003/redis.log | 192.168.1.102 |
| 7004 | 7004 | 17004 | /data/redis/7004 | /data/redis/7004/redis.log | 192.168.1.102 |
| 7005 | 7005 | 17005 | /data/redis/7005 | /data/redis/7005/redis.log | 192.168.1.102 |
创建目录:
mkdir -p /data/redis/{7000,7001,7002,7003,7004,7005}有人喜欢把所有配置写在一个文件里,用不同的port启动多个实例。理论上可以,但排查问题时很容易分不清当前操作的是哪个实例。独立目录加独立配置文件的思路,后面不管是看日志、备份数据还是迁移节点,都会非常省心。
3.2 配置文件逐条详解:不只是把开关打开
Minimal 配置其实很简单,核心就是开启 cluster 模式、指定端口和数据目录。但生产环境如果只开这几个开关,后面大概率会遇到一堆问题。这个了看一个完整的配置文件示例,以 7000 端口为例:
bind 0.0.0.0 protected-mode no port 7000 daemonize yes pidfile /var/run/redis_7000.pid logfile "/data/redis/7000/redis.log" dir /data/redis/7000 appendonly yes appendfilename "appendonly.aof" auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 cluster-require-full-coverage no这里每一条都值得展开说。bind 0.0.0.0表示监听所有网卡,如果机器有多块网卡,建议改成内网 IP,避免 Redis 端口暴露到不必要的网络区域。protected-mode no是因为集群节点之间、客户端与节点之间都可能需要直连,默认的 protected-mode 只允许本机访问,不关掉的话从其他机器连过来会被拒绝,但这个开关要结合防火墙来使用,千万别裸奔在公网环境。
daemonize yes让 Redis 以守护进程方式运行,配合pidfile可以在运维脚本里方便地做 kill、启停。如果用的是 systemd 管理,可以改成daemonize no配合supervised systemd,这里为了演示简单,先用 daemonize。
appendonly yes在 Redis 7 中默认就是开启的,但我还是建议显式写出来。Redis 7 开始 AOF 机制升级为多部分文件格式,会在 data 目录下生成 base 文件、incr 文件和 manifest 清单文件,跟老版本只有一个 appendonly.aof 的行为有所不同。如果你在线上见过appendonly.aof.1.base.rdb、appendonly.aof.1.incr.aof之类的文件,别慌,那就是 Redis 7 的新 AOF 结构。
最关键的是 cluster 相关配置:
cluster-enabled yes:这一行决定实例是否以集群模式运行。cluster-config-file nodes-7000.conf:这个文件由 Redis 自己维护,不需要手动创建。节点启动后,它会把自己知道的集群节点信息、槽位分配情况、节点状态写入这个文件。注意:如果这个文件残留了上一次集群的信息,重新搭建集群时很可能报 “Node is not empty” 或 “Slot already busy” 的错误,所以每次做实验时记得清理这些文件。cluster-node-timeout 15000:节点超时时间,单位毫秒。主节点超过这个时间没有响应,从节点就开始发起选举。这个值太短容易因为网络抖动误判故障,太长则故障自动切换会很慢。我一般设 15 秒左右。cluster-require-full-coverage no:这个参数值得重点解释。默认值是 yes,表示只有所有 16384 个槽都被节点服务时,集群才对外提供服务。如果某个主节点挂了,它负责的槽位变成未覆盖状态,整个集群就会拒绝所有读写请求。生产环境我一般会设为 no,这样即使部分槽位不可用,其他槽位的数据读写还能继续,但这意味着不在线的那部分数据对客户端来说会报错,你需要结合业务容忍度来判断。重要系统里,最好还是保留 yes,宁可让请求失败,也不要在故障时返回不完整的数据。
3.3 从节点和密码认证:生产环境绕不开的两个点
上面的配置是以 7000 主节点为例的。从节点配置其实几乎一样,唯一的区别是你可以指定它从哪个主节点复制,但实际上 Redis Cluster 创建集群时,通过--cluster-replicas 1参数会自动分配主从关系,不需要在配置文件里手写replicaof,这点和主从复制模式的配置方式很不一样。所以配置文件基本可以照抄,只要把端口、pidfile、logfile、dir 对应的数字改成实际端口即可。
如果你的业务要求访问必须带密码,那需要在每个配置文件中额外加上两行,注意一定是两条:
requirepass yourstrongpassword masterauth yourstrongpasswordrequirepass是客户端连接时需要的密码,masterauth是主从之间进行复制时的认证密码。从节点要访问主节点去拉取数据,必须由主节点进行身份认证,所以这两条要同时设置。在集群模式下,配置文件里加了密码之后,后续用redis-cli创建集群和检查集群时,都要带上密码参数,否则会提示认证失败。
4. 实例启动与集群初始化
4.1 把六个节点全部启动起来
配置写好后,逐个启动。手动启动的方法是一个一个执行redis-server,例如:
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis-7000.conf /usr/local/redis/bin/redis-server /usr/local/redis/conf/redis-7001.conf如果你配置了两台机器,那么 7000 到 7002 在机器 A 启动,7003 到 7005 在机器 B 启动。启动完成后,用ps -ef | grep redis或redis-cli -p 7000 ping检查一下,能收到 PONG 就说明基本正常。这时集群还没有建立,每个节点都只知道自己是孤立的 cluster 节点,可以用redis-cli -p 7000 cluster info看一下状态,大概率cluster_state:fail、cluster_slots_assigned:0,这是正常的,因为槽位还没分配。
启动阶段容易踩的坑是端口没监听成功。用netstat -lntp或ss -lntp检查 7000 到 7005 端口,同时确认 17000 到 17005 也被监听。如果端口没起来,八成是配置文件里的bind或protected-mode不对,或者端口被占用。注意cluster-config-file指向的目录必须可写,否则节点启动时无法生成 nodes 配置文件,也会导致启动失败。
4.2 一键创建集群:槽位分配和主从关系自动完成
Redis 5 之前创建集群要额外装 ruby 的redis-trib.rb,5.0 之后直接用redis-cli --cluster子命令就可以完成,Redis 7 同样如此。假设两台机器 IP 已确定,推荐用下面的方式把 192.168.1.101 上的 7000 到 7002 和 192.168.1.102 上的 7003 到 7005 联合起来:
/usr/local/redis/bin/redis-cli --cluster create \ 192.168.1.101:7000 192.168.1.101:7001 192.168.1.101:7002 \ 192.168.1.102:7003 192.168.1.102:7004 192.168.1.102:7005 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点配一个从节点。执行后,redis-cli 会自动计算槽位分配方案,一般会把 0 到 5460 给第一个主节点,5461 到 10922 给第二个,10923 到 16383 给第三个,然后为每个主节点选择副本。它会打印出完整的分配计划并让你确认,交互式输入yes即可确认。
如果你配置了密码,要在命令里加上-a yourstrongpassword。不加的话,创建过程中会因为无法认证而失败,错误信息一般比较明显,比如ERR Client sent AUTH, but no password is set。注意确认密码的两个方向:既要确认客户端认证,也要确认主从之间的 masterauth 配好了,不然后面复制链路起不来。
4.3 验证集群状态:三步确认真的没问题
创建完成后不要急着写业务,先做三步验证。
第一步,看集群概览:
/usr/local/redis/bin/redis-cli -p 7000 cluster info重点看cluster_state:ok、cluster_slots_assigned:16384、cluster_known_nodes:6这几个关键行。只要cluster_state不是 ok,后面读写请求很可能直接报CLUSTERDOWN。
第二步,看节点拓扑和主从关系:
/usr/local/redis/bin/redis-cli -p 7000 cluster nodes | sort -k9输出中每行代表一个节点,包含节点 ID、IP 端口、角色、从属关系等。看到master后面跟着slave节点,并且每个 master 都有对应的 slave,说明主从分配正常。如果某个节点后面一直标着disconnected或fail?,就要回到网络层面排查。
第三步,用集群自带的检查命令做一次全面体检:
/usr/local/redis/bin/redis-cli --cluster check 192.168.1.101:7000这个命令会检查槽位覆盖情况、节点间连接状态、从节点复制是否正常,能一次性把大部分常见问题暴露出来。如果输出里有All 16384 slots covered这种字样,就可以放心了。
验证完毕后,我习惯顺手存一个数据测试一下跨节点读写:
/usr/local/redis/bin/redis-cli -c -p 7000 set greeting "hello cluster" /usr/local/redis/bin/redis-cli -c -p 7000 get greeting-c选项让客户端自动处理 MOVED 重定向,如果键被分到其他节点,-c模式会帮你跳转,这样能确认槽位分配和数据定位逻辑是否正常。
5. 集群运行原理与数据分片机制
5.1 哈希槽与 CRC16:为什么有时候跳到另一个节点
Redis Cluster 的数据分片不像传统一致性哈希那样用哈希环,而是采用固定 16384 个哈希槽。每个 key 通过 CRC16 算法算出一个 16 位的值,然后对 16384 取模,得到槽号。集群创建时,这 16384 个槽均匀分给各个主节点,客户端写入某个 key 时,Redis 会计算它属于哪个槽,然后由该槽所在的主节点提供服务。
写代码时你可能会遇到一个现象:用redis-cli不带-c参数访问某个节点的 key,在一些情况下会返回MOVED错误。这不是故障,而是集群的正常机制——节点发现这个 key 应该由另一个节点服务时,会返回MOVED重定向信息,告诉客户端去访问正确的节点。所以生产环境用客户端时,一定要选择支持集群模式的客户端,比如 Java 的 Lettuce、Jedis Cluster、Go 的 go-redis、Python 的 redis-py-cluster,它们会在内部自动处理重定向和路由表更新,不需要你手动拼节点地址。
关于为什么是 16384 而不是 65536,Redis 作者在社区里有解释:16384 个槽位在节点间的心跳包中可以用 2KB 的位图来表示,节点数量在千个以内时非常轻量;如果扩大到 65536,每个心跳包会多出 8KB 的开销,并且节点数量很多时,迁移复杂度也会非线性增长。16384 是在“足够细的分片”和“通信开销合理”之间做的一个折中。
5.2 集群总线、gossip 心跳与故障转移
集群节点之间通过集群总线通信,也就是业务端口加 10000 的那个端口。这部分的流量和正常客户端访问是分离的,专门用来传播节点状态、槽位分配、主从变更等元数据。每个节点不是把所有信息都广播给所有人,而是用 gossip 协议随机挑几个节点交换状态,最后整个集群的状态会趋于一致。
故障检测和自动切换走向成熟要经历几个状态。首先,某个主节点如果超过cluster-node-timeout时间没有心跳,会被其他节点标记为 PFAIL,也就是疑似下线。如果集群中多个节点都认为它 PFAIL,并且这个信息通过 gossip 传播开,就会升级为 FAIL 状态。之后,这个失败主节点的从节点会发起故障转移,选举机制基于 Raft 算法,从节点得到大多数节点投票后提升为新的主节点,并接管原主节点负责的哈希槽。
这个过程中有几个关键细节:一是从节点选举时要求原主节点已经 FAIL,而且必须等待一段随机时间,避免多个从节点同时争抢;二是选举成功后,新主节点会广播配置纪元,集群里其他节点会更新自己的视图。因此,一台物理机宕机时,只要每个主节点都有健康的从节点,业务就能在十几秒内自动恢复,这就是 Cluster 高可用的核心能力。
5.3 多 key 操作与 hash tag:别让你的事务和 Lua 脚本失效
集群模式下有个限制,跨槽位的多 key 操作是不支持的。比如在 Redis 里执行MGET key1 key2,如果key1和key2算出的槽位不同,在集群模式下会直接报错。如果业务确实需要这种联合查询,就得用 hash tag 这个机制。
hash tag 的用法是在 key 中加上花括号,CRC16 计算槽号时只对花括号内的子串进行计算。比如{user:1001}:name和{user:1001}:age,虽然 key 字符串不同,但因为花括号里的内容都是user:1001,它们会落在同一个槽位上,从而支持多 key 操作、事务和 Lua 脚本。
这个设计非常实用,但也容易用错。要注意的是:花括号内没有内容,比如{}:name,那 Redis 还是会对整个 key 做 CRC16,导致分片不符合预期。另外,hash tag 会让某些 key 集中在少数几个槽位上,如果所有热点 key 都放在了同一个 tag 里,会导致数据倾斜,节点之间内存和流量不均。所以设计 tag 时,要考虑业务能否接受单槽数据量上限。
6. 扩容、缩容与在线迁移
6.1 动态增加主节点:先加节点,再迁移槽位
集群搭建不是一锤子买卖,业务增长后免不了要扩容。Redis Cluster 支持在线增加节点,整个过程不用停服务。假设你要将一台新机器 192.168.1.103 上的 7006 实例加入集群,先用和之前一样的配置方式启动一个 cluster-enabled 的实例,然后执行:
/usr/local/redis/bin/redis-cli --cluster add-node 192.168.1.103:7006 192.168.1.101:7000后面的192.168.1.101:7000表示让集群里任意一个现有节点作为联系人,推荐用当前任意的 master 或任意节点都可以。新节点加入后,它暂时是一?个空主节点,不负责任何槽位。接着需要把原有节点的部分槽位迁移给它:
/usr/local/redis/bin/redis-cli --cluster reshard 192.168.1.101:7000交互式命令会问你几个问题:要迁移多少个槽、接收槽的节点 ID 是什么、从哪些源节点迁出槽。如果你想自动化,可以直接用非交互方式:
/usr/local/redis/bin/redis-cli --cluster reshard 192.168.1.101:7000 \ --cluster-from <source_node_id> \ --cluster-to <target_node_id> \ --cluster-slots 2048 \ --cluster-yes这里--cluster-slots 2048表示迁移 2048 个槽,你也可以迁移 4096 或 5461,比例看业务压力。迁移过程中,Redis 会先把槽内的数据从源节点导出,再导入到目标节点,期间对客户端来说 key 可能短暂处于 ASK 状态。支持集群模式的客户端会自动处理 ASK 重定向,所以一般不需要停机。迁移结束后,可以用cluster info和--cluster check重新确认槽位分配是否健康。
6.2 动态增加从节点:给某个主节点加副本
如果你发现某个主节点的压力特别大,或者集群里某个主节点没有从节点,可以单独给它加一个从节点:
/usr/local/redis/bin/redis-cli --cluster add-node 192.168.1.103:7007 192.168.1.101:7000 \ --cluster-slave --cluster-master-id <master_node_id>--cluster-slave表示新节点以从节点身份加入,--cluster-master-id指定它的主节点 ID。执行后,这个新节点会立刻向主节点发起全量同步,把数据拉取过来,然后持续保持复制。如果集群里还有体量较小的主节点,也可以用这种方式针对性地补齐副本,降低单点风险。
6.3 缩容与删除节点:别忘了先迁走数据
缩容比扩容要小心一些。如果你要下线某个主节点,不能直接del-node,因为它身上还有槽位和数据。正确的操作是先用reshard把它负责的槽位移交给其他节点,等它变成一个真正意义上的空节点,再执行删除命令。
/usr/local/redis/bin/redis-cli --cluster del-node 192.168.1.101:7000 <node_id>如果节点上还有槽位,命令会直接报错,提示Node has slots。所以删节点的顺序一定是:先迁移槽位,再下发删除命令。删除从节点就简单多了,只要它已经没有复制关系,直接 del-node 即可。还有一个细节:集群里如果因为多次增减节点留下了孤儿节点,内存和连接资源会被浪费,建议定期用--cluster check检查一下节点状态和主从关系。
7. 常见问题与排查技巧实录
7.1 “Node is not empty”与残留配置导致集群创建失败
这是搭建时非常常见的问题,尤其是中断过一次创建流程之后。原因一般是节点的dir数据目录下已经存在appendonly.aof、dump.rdb等数据文件,或者nodes-*.conf里已经记录了旧的集群信息。Redis 看到节点里已经有槽位或数据,就会拒绝加入新集群。
处理方法按顺序来:先停掉 redis-server,删除该端口目录下的nodes-*.conf,同时清掉数据文件,或者把目录整个清空。如果不想删数据,也可以用redis-cli --cluster fix修复,但实验环境下建议直接清空。还有一个命令是redis-cli -p 7000 cluster reset,它会重置节点的槽位和集群状态,但数据文件依旧可能存在,所以最稳妥的办法还是确认目录干净之后再重新创建。
7.2 节点一直 PFAIL/disconnected:大概率是集群总线端口不通
集群创建成功后,过一段时间用cluster nodes查看,如果某个节点状态一直显示disconnected或者从 PFAIL 转成 FAIL,但客户端访问其他节点都正常,那八成是节点之间的业务端口 + 10000的集群总线端口被防火墙、安全组拦截了。很多人只放了 7000 到 7005,却忘了放 17000 到 17005,节点之间无法交换心跳元数据,故障检测自然无法正常收敛。
这类问题排查时,先ping看通不通,再用telnet 192.168.1.102 17003测一下端口通不通,通不了就检查防火墙规则和安全组。别一上来就重启节点,很多时候重启完还是同样的状态,问题在网络层面。
7.3 CLUSTERDOWN 与 cluster_require_full_coverage 的取舍
集群创建好以后,某个主节点宕机,如果你设置了cluster-require-full-coverage yes,这时整个集群都会进入只读或直接拒绝服务的状态,客户端报CLUSTERDOWN Hash slot not served。这是默认保护机制,为了防止读到不完整的数据。但我处理线上问题时,会更倾向把这个参数设成 no,然后依赖业务侧对错误进行降级处理。前提是你得观察业务是否接受部分读写失败,例如某些非核心功能可以容忍短时不可用,而核心账务类系统则不建议。
如果你当前已经因为主节点故障导致整个集群不可用,临时处理办法是先把没挂的主节点对应的从节点提升上来,或者手动把宕机主节点的槽位标记为已迁移,再用CLUSTER FAILOVER或redis-cli --cluster fix修复。修复前一定要搞清楚故障根因,不然就算集群恢复了,过一阵又会再次出问题。
7.4 客户端大量 MOVED 重定向:路由缓存没跟上
有时候集群运维操作做完了,比如 reshard 或节点变更,但客户端业务依然报大批量MOVED错误。这是因为部分客户端对路由表的缓存是懒更新的,只有当它访问到某个节点并收到MOVED响应时,才会更新本地缓存。正常的集群客户端会自己处理这个问题,但如果你的客户端没有开启集群模式,或者你用的是普通连接池,那就会频繁遇到这种情况,看起来像是“数据丢了”。
遇到这类问题,先确认代码里连接 Redis 的地址是否配置成集群模式。以 Java 为例,Lettuce 要使用RedisClusterClient,Jedis 要使用JedisCluster;以 Python 为例,要使用redis.cluster.RedisCluster。如果你只是用一个普通的redis.Redis(host, port)去连集群,跨槽访问必然出错。
7.5 常见问题速查表
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 创建集群报 “Slot already busy” | nodes.conf 或数据目录残留 | 清空节点数据,执行 cluster reset 或删目录 |
| 某个节点一直 PFAIL | 集群总线端口不通 | 放行 port+10000 端口,检查防火墙 |
| 读写报 CLUSTERDOWN | 有槽位未覆盖或主节点宕机 | 恢复故障节点,或调整 require-full-coverage |
| 跨节点访问报 MOVED | 客户端没启用集群模式 | 使用支持集群的客户端并正确配置 |
| 主从复制一直失败 | masterauth 未配置或密码不一致 | 确认 requirepass 与 masterauth 一致 |
| 迁移槽位后部分 key 访问失败 | 迁移过程中客户端 ASK 重定向处理不当 | 使用支持 ASK 的集群客户端,重试机制 |
| 集群节点数增多但槽位分布不均 | reshard 未执行或执行不完整 | 用 reshard 重新平衡,再用 check 验证 |
7.6 一些运维心得与监控建议
集群搭建起来只是第一步,运行期维护才是重头戏。建议把内存使用率、连接数、慢查询日志、主从同步延迟、槽位迁移状态这几个指标纳入监控。Redis Cluster 提供了cluster info、cluster nodes这些命令,可以定时采集并做阈值告警,比如cluster_state不是 ok、主节点数量变动、fail 节点数量增加都要立刻告警。
还有一个容易忽略的点:日志和数据的磁盘空间。Redis 7 的 AOF 多部分文件会让文件数量变多,如果dir满了,RDB 快照或 AOF 写入都会失败,这时候数据其实是带病运行的。我吃过这个亏,当时集群看起来正常,实际上某个节点的 AOF 已经停了很久,后面主从切换后用恢复文件才发现数据落后了一大截。所以磁盘空间监控一定要配置好,建议每天检查df -h。
动集群操作之前,最好先做备份。Redis Cluster 的备份思路不是整个集群快照,而是对每个主节点做 BGSAVE 或把 AOF 拷贝出来。不要只备份一个节点,因为每个节点只有一部分数据,多个节点的备份组合起来才是完整数据集。恢复时,把每个节点的数据放到对应目录,再按原来的拓扑启动,集群会自动重新构建元数据。
我个人在多次操作中逐渐养成的习惯是:生产环境动手前,先在测试集群里把同样的操作完整走一遍。比如 reshard、add-node、failover,这些操作在测试环境验证过,到生产环境执行时心里就有底了。集群这种分布式系统,最怕的不是原理复杂,而是某一个细节被忽视,导致全链路抖动。把细节和习惯固化下来,比收藏多少教程都管用。