
1. 为什么要在虚拟机上搭建 Redis Cluster1.1 从单机 Redis 到集群的必然演进很多同学刚开始接触 Redis 的时候都是在本地或者一台测试机上装一个单实例跑跑缓存、做做队列感觉已经够用了。但一旦业务量上来单实例的问题就会集中爆发内存受限于单机物理上限、QPS 顶到 CPU 瓶颈、主节点一挂整个缓存层直接雪崩。这时候你自然会想到 Redis 官方提供的分布式方案——Redis Cluster。Redis Cluster 是 Redis 3.0 之后官方推出的分布式集群方案核心能力有三个数据分片、高可用和去中心化。它把整个键空间划分成 16384 个哈希槽slot每个主节点负责一部分槽位客户端根据 key 计算 CRC16 再对 16384 取模就能定位到具体节点。同时每个主节点可以挂若干从节点做副本主挂了从顶上保证可用性。节点之间通过 Gossip 协议互相通信没有中心节点任意节点都能响应客户端请求并转发。那为什么要在虚拟机里搭而不是直接上云或者用 Docker这个问题我被问过很多次。原因其实很实在第一虚拟机最接近真实的生产环境你能完整地控制网络、端口、内存、磁盘能模拟出节点宕机、网络分区这些真实故障第二成本低一台物理机开六台虚拟机就能跑一个三主三从的完整集群比买六台云主机便宜太多第三可反复折腾快照一打搞坏了直接回滚学习成本几乎为零。Docker 虽然轻量但网络模型和真实服务器差异较大排查问题时容易混淆是容器网络的问题还是 Redis 本身的问题。1.2 三主三从的最小生产级拓扑Redis Cluster 官方要求至少 3 个主节点这是因为集群的故障转移需要多数派投票3 个主节点能容忍 1 个挂掉。如果只有 2 个主节点挂一个就凑不齐多数派集群直接不可用。所以最小可用拓扑是 3 主加上每个主配 1 个从就是经典的3 主 3 从共 6 个节点。这个拓扑的意义在于任意一个主节点宕机它的从节点会被提升为新主集群继续对外服务槽位覆盖不缺失。如果只搭 3 个主不搭从那挂一个主它负责的那 5461 个槽就没人管了整个集群进入 fail 状态客户端写入直接报CLUSTERDOWN。在虚拟机里我一般这样规划6 台虚拟机每台 2 核 4G 内存系统用常见的 Linux 发行版网卡桥接模式让它们在同一网段互通。IP 规划成 192.168.100.11 到 192.168.100.16端口统一用 6379这样最直观。当然如果你机器资源紧张也可以一台虚拟机跑多个实例用不同端口区分但那样故障隔离性差学习阶段不推荐。提示虚拟机内存别给太小Redis 默认会 fork 子进程做 RDB 持久化内存不足时 fork 会失败甚至触发 OOM。4G 是比较舒服的起步配置。1.3 这套方案适合谁参考如果你正在准备面试被问到 Redis 集群、想自己动手验证分片和故障转移的原理、或者公司项目要上 Redis Cluster 但你没在生产环境练过手那这套虚拟机方案就是为你准备的。它不需要你有多深的运维功底只要会基本的 Linux 命令、能看懂配置文件跟着走一遍就能跑起来。后面我还会讲怎么模拟节点宕机、怎么观察槽位迁移、怎么排查集群起不来的常见坑这些都是文档里不会细说但实际一定会遇到的东西。2. 搭建前的环境准备与核心参数规划2.1 虚拟机与网络的基础配置先把 6 台虚拟机准备好系统装完后有几件事必须做。第一是关闭防火墙或者放行端口Redis Cluster 不光要用 6379 这个客户端端口还需要一个集群总线端口默认是客户端端口加 10000也就是 16379。节点之间靠这个端口做 Gossip 通信、故障检测、配置传播。很多人集群建不起来就是只开了 6379 没开 16379。第二是设置主机名和 hosts 解析。虽然用 IP 也能跑但集群节点握手后会把 IP 写进配置一旦 IP 变了集群就乱。我习惯给每台机器设个主机名比如 node1 到 node6然后在每台的/etc/hosts里把 6 个名字和 IP 对应上。这样配置文件里写主机名可读性好迁移也方便。第三是时间同步。集群节点之间判断故障是靠时间戳和心跳的如果各机器时间差太大会出现误判节点下线的情况。用系统自带的时间同步服务保证 6 台机器时间一致就行。第四是调整系统参数。Redis 在高并发下需要大量的文件描述符和网络连接默认的 1024 往往不够。需要改/etc/security/limits.conf把nofile提到 65535。另外如果开启了 RDB 持久化建议把vm.overcommit_memory设为 1避免 fork 时内存分配失败。2.2 Redis 版本选择与安装方式版本上我建议用Redis 6.x 或 7.x 的稳定版。5.0 之前集群功能虽然能用但运维工具和稳定性差一些6.0 引入了多线程 IO 和 ACL7.0 在集群和持久化上又有优化。学习阶段用 6.2 或 7.2 都很稳。安装方式有两种包管理器直接装或者源码编译。包管理器省事但版本可能偏旧而且配置文件路径和源码安装不一样。我推荐源码编译因为你能完全控制安装路径、配置文件位置也方便后续升级。步骤就是下载源码包、解压、make编译、make install安装。编译前确保装了 gcc 和 make。编译完成后redis-server、redis-cli、redis-trib新版本已集成到 redis-cli 里这些工具就都有了。这里有个细节Redis 6 之后集群管理命令已经合并进redis-cli用redis-cli --cluster就能创建和管理集群不再需要单独的redis-trib.rb脚本也就省去了装 Ruby 环境的麻烦。这是个很大的便利老教程里还在讲装 Ruby 的可以直接跳过。2.3 关键配置项逐个拆解每个节点的redis.conf里有几个参数是集群专属的必须配对配错一个集群就起不来。我逐个说清楚它们的作用和取值逻辑。cluster-enabled yes这是总开关不开这个 Redis 就是普通单机模式不会参与集群。默认是注释掉的必须手动打开。cluster-config-file nodes.conf这是节点自己维护的集群状态文件记录着本节点认识的其它节点、槽位分配、主从关系等。这个文件不要手动改Redis 自己会写。每次节点重启会读它恢复集群状态。如果这个文件丢了节点会以全新身份加入可能导致集群混乱。cluster-node-timeout 15000节点超时时间单位毫秒。意思是如果一个节点 15 秒没响应心跳就认为它可能挂了。这个值不能太小太小会因网络抖动误判也不能太大太大故障转移慢。15000 是官方推荐值内网环境够用。如果虚拟机性能差、网络延迟高可以适当调到 20000。cluster-require-full-coverage no这个参数很关键。默认是 yes意思是只要有任何一个槽位没有节点负责整个集群就拒绝所有请求。生产环境一般设成 no这样即使某个主节点挂了还没完成转移其它槽位的数据依然可读写只是挂掉那部分不可用。学习阶段建议设 no方便你观察部分可用状态。appendonly yes集群模式下强烈建议开启 AOF 持久化。因为集群节点重启后要从持久化文件恢复数据RDB 可能丢较多数据。AOF 配合appendfsync everysec在性能和安全性之间比较平衡。bind 和 protected-mode虚拟机之间要互通bind要写成0.0.0.0或者具体的内网 IPprotected-mode设成 no否则外部连不上。但注意这只在内网环境这么做公网环境绝不能这么配。port 和 cluster-announce 系列如果节点在 NAT 后面或者端口做了映射需要用cluster-announce-ip和cluster-announce-port告诉其它节点自己的真实地址。纯内网直连的话不用配。把这些参数整理成一张表方便你对照检查配置项推荐值作用说明cluster-enabledyes开启集群模式的总开关cluster-config-filenodes.conf节点集群状态持久化文件cluster-node-timeout15000判定节点下线的超时毫秒数cluster-require-full-coverageno部分槽位不可用时是否拒绝全部请求appendonlyyes开启 AOF 持久化appendfsynceverysecAOF 刷盘策略每秒一次bind0.0.0.0监听所有网卡仅内网protected-modeno关闭保护模式仅内网daemonizeyes后台运行3. 六节点集群的完整搭建实操3.1 批量生成节点配置目录6 台机器如果一台台手动配容易配错还费时间。我的做法是写一个简单的 shell 脚本在每台机器上执行自动创建目录结构、生成配置文件。目录结构我习惯这样/data/redis/6379/放配置和数据/data/redis/logs/放日志/data/redis/pid/放 pid 文件。这样每个实例的数据、日志、配置都隔离排查问题清晰。配置文件的生成可以用模板加变量替换。比如把redis.conf做成模板里面的端口、绑定 IP、节点超时这些用占位符脚本读取本机 IP 后替换生成最终配置。这样 6 台机器跑同一个脚本生成的配置只有 IP 不同减少人为失误。脚本里还要做几件事创建数据目录、设置目录权限Redis 进程用户要有读写权限、把vm.overcommit_memory和文件描述符限制写进系统配置。这些一次性做完后面启动就顺了。注意如果你是在一台机器上跑 6 个实例用不同端口那cluster-config-file和appendonly文件必须放在各自独立的目录否则会互相覆盖集群直接崩。3.2 启动所有节点并验证单机状态配置生成后逐个启动节点。启动命令是redis-server /data/redis/6379/redis.conf。启动后先用ps确认进程在再用redis-cli -h 本机IP -p 6379 ping看是否返回 PONG。这一步是确认每个节点作为单机是活的。然后重点看集群状态redis-cli -h 本机IP -p 6379 cluster info。刚启动还没组集群时cluster_state应该是failcluster_known_nodes是 1cluster_size是 0。这是正常的因为还没握手。如果这里cluster_enabled显示 0说明配置文件里cluster-enabled没生效回去检查配置。再看cluster nodes应该只有自己一个节点角色是 master没有槽位。如果这一步就有异常比如端口没监听、日志报错先解决单机问题再往下走不要急着组集群。我见过太多人跳过这步结果组集群时各种超时回头排查发现是某个节点根本没起来。日志文件这时候要重点看路径在配置里指定的logfile。正常启动日志会显示Ready to accept connections集群模式下还会显示Node configuration loaded或者Cluster enabled。有报错就按报错信息查常见的是端口占用、权限不足、配置文件语法错误。3.3 用 redis-cli 创建三主三从集群所有节点都确认单机正常后就可以创建集群了。在 Redis 6 以上版本一条命令搞定redis-cli --cluster create \ 192.168.100.11:6379 \ 192.168.100.12:6379 \ 192.168.100.13:6379 \ 192.168.100.14:6379 \ 192.168.100.15:6379 \ 192.168.100.16:6379 \ --cluster-replicas 1这里--cluster-replicas 1的意思是每个主节点配 1 个从节点。命令执行后redis-cli 会自动计算槽位分配方案把 16384 个槽平均分给前 3 个节点作为主后 3 个作为从跟随对应的主。它会打印出一个分配方案让你确认输入yes回车。分配逻辑是这样的前 3 个 IP 被选为主节点分别负责 0-5460、5461-10922、10923-16383 三段槽位。后 3 个节点作为从节点随机或者按顺序跟随主节点。这个分配不是固定的redis-cli 会根据节点顺序和内部算法决定但保证每个主分到的槽数尽量均衡。执行过程中你会看到类似 Performing hash slots allocation on 6 nodes...的输出然后是M: xxx和S: xxx标记主从。最后显示[OK] All 16384 slots covered就说明集群创建成功了。如果卡在Waiting for the cluster to join很久多半是总线端口 16379 没通回去检查防火墙。3.4 集群健康状态检查清单集群建好后别急着用先做一轮完整体检。我整理了一个检查清单按顺序过一遍第一redis-cli -c -h 任意节点IP -p 6379 cluster info看cluster_state是不是okcluster_slots_assigned是不是 16384cluster_known_nodes是不是 6cluster_size是不是 3。这四个数字对了集群基本健康。第二cluster nodes看每个节点的角色、槽位、主从关系。主节点后面跟着它的从节点 ID从节点会显示slaveof哪个主。确认 3 主 3 从关系正确没有节点处于fail或handshake状态。第三做一次实际读写。用redis-cli -c的集群模式连上去set foo bar然后get foo。注意-c参数不能少它让客户端自动跟随重定向。如果你 set 一个 key 返回MOVED错误说明客户端没开集群模式加上-c就好。第四验证分片。故意 set 几个不同的 key观察它们被分配到不同节点。你可以用cluster keyslot key名命令算出某个 key 属于哪个槽再对照槽位分配表看落在哪个节点。这一步能直观理解分片原理。第五检查持久化文件是否生成。去每个节点的数据目录看有没有appendonly.aof和nodes.conf。这两个文件存在说明持久化和集群状态都在正常写入。4. 故障模拟与集群行为观察4.1 手动下线一个主节点看故障转移集群搭好了不模拟故障等于白搭。最经典的实验就是手动干掉一个主节点观察从节点自动顶替的过程。先找到某个主节点的进程直接kill掉模拟宕机。然后在另一个节点上持续执行cluster nodes你会看到这样的变化刚开始那个主节点状态变成fail?表示疑似下线过了cluster-node-timeout时间后变成fail确认下线接着它的从节点开始竞选日志里出现Starting a failover election竞选成功后从节点变成 master接管原来的槽位状态恢复ok。整个过程通常在 15 到 30 秒内完成。这个实验有几个观察点值得注意。第一故障转移期间被挂主节点负责的那部分槽位不可用客户端访问这些 key 会报CLUSTERDOWN或者超时但其它槽位正常。这就是为什么前面建议cluster-require-full-coverage设 no。第二从节点提升后原来的主节点如果重新启动它会以从节点身份加入跟随新的主不会抢回主位。这是 Redis Cluster 的设计避免脑裂。第三整个转移过程不需要人工干预完全自动。4.2 模拟网络分区与脑裂防护比宕机更复杂的是网络分区。你可以用防火墙规则把某个主节点和集群其它节点隔离开模拟它失联但进程还活着。这时候集群多数派会认为它下线触发故障转移从节点提升。而那个被隔离的主节点自己还认为自己是主继续接受它那侧客户端的写入这就形成了脑裂。Redis Cluster 用cluster-node-timeout和多数派投票来缓解这个问题。被隔离的节点因为收不到多数派的心跳确认无法完成故障转移投票最终会把自己降级。当网络恢复后它会以从节点身份重新加入并且它的数据会被新主的数据覆盖因为新主在隔离期间可能已经写入了新数据。所以集群模式下数据一致性是靠主从复制和故障转移保证的但极端分区下仍可能丢少量数据这是 CAP 权衡的结果要心里有数。做这个实验时恢复网络后一定要检查cluster nodes确认被隔离节点已经正确降级并同步。如果它还固执地认为自己是主可能需要手动cluster reset再重新加入。这个坑我在测试环境踩过原因是隔离时间太长超过了配置的容忍度节点状态错乱。4.3 槽位迁移与重新分片实操集群运行中难免要扩缩容比如加一个新节点进来分担压力或者下线一个节点把它的槽位挪走。这就是重新分片resharding。Redis 提供了redis-cli --cluster reshard命令来做这件事。假设要把某个主节点的 1000 个槽迁移到新节点。命令会问你迁移多少个槽、迁到哪个节点、从哪些节点迁。你可以指定从某一个源迁也可以从所有主节点平均迁。执行过程中槽位是一个个迁移的每个槽迁移时源节点会把该槽的 key 逐个MIGRATE到目标节点迁移完成后更新集群配置。这个过程是在线的不影响其它槽的读写但迁移中的槽如果有请求进来客户端会收到ASK重定向需要客户端支持。迁移完成后用cluster nodes确认槽位归属已经更新cluster info确认cluster_slots_assigned还是 16384。这里有个经验迁移大批量槽时最好在业务低峰期做因为MIGRATE是同步阻塞的key 多的时候会卡住源节点。可以分批迁每次几百个槽观察负载再继续。5. 常见问题排查与避坑经验5.1 集群创建失败的典型原因速查集群创建阶段失败是最常见的我把踩过的坑整理成一张速查表现象可能原因排查方法Waiting for the cluster to join 卡住总线端口 16379 未放行telnet 目标IP 16379 测试连通性Node is not empty 报错节点数据目录有旧集群数据删除 nodes.conf 和 aof/rdb 后重启cluster_state 一直 fail槽位未全覆盖或节点未握手cluster info 看 slots_assigned 是否 16384节点显示 handshake 状态节点间网络不通或端口错检查 bind、防火墙、总线端口MOVED 重定向频繁客户端未开集群模式redis-cli 加 -c 参数从节点不跟随主复制关系未建立cluster nodes 看 slaveof 是否正确其中Node is not empty这个坑特别常见。如果你之前在这台机器上跑过集群数据目录里残留了nodes.conf新集群创建时节点会带着旧身份加入导致冲突。解决办法就是创建前清空所有节点的数据目录确保是干净状态。我现在的习惯是每次重建集群前先跑一个清理脚本把所有节点的nodes.conf、appendonly.aof、dump.rdb全删掉再启动。5.2 运行期的性能与稳定性问题集群跑起来后问题往往出在性能和稳定性上。第一个常见问题是内存碎片。Redis 频繁增删 key 后内存碎片率会升高used_memory_rss远大于used_memory。这时候可以开启activedefrag yes让 Redis 自动整理碎片但会消耗 CPU。或者干脆在低峰期重启节点重启后内存会重新整理。第二个问题是大 key 和热 key。集群分片是按 key 分的但如果某个 key 特别大比如一个 list 存了几十万条迁移和访问都会很慢。热 key 则是某个 key 被高频访问压力全集中在一个节点上。解决办法是拆分大 key热 key 做本地缓存或者加随机后缀分散。这些在单机时可能不明显集群下会被放大。第三个问题是主从复制延迟。如果主节点写入量大从节点同步跟不上故障转移时可能丢较多数据。可以监控master_repl_offset和从节点的slave_repl_offset差值差值持续增大说明复制有压力。优化方向是减少大 key 写入、提高网络带宽、或者调整repl-backlog-size。5.3 我踩过的三个真实坑第一个坑是配置文件里的 bind 写成了 127.0.0.1。单机测试时这么写没问题但集群节点之间要互相通信绑到回环地址后其它节点根本连不上创建集群时一直卡在 join。改成内网 IP 或 0.0.0.0 后立刻就好了。这个坑隐蔽在于单机 ping 是通的容易误判。第二个坑是虚拟机内存给太小导致 fork 失败。有次给每台机器只分了 1G 内存Redis 数据量涨到几百兆后做 RDB 持久化时 fork 子进程失败日志报Cant save in background: fork: Cannot allocate memory。后来把vm.overcommit_memory设为 1内存加到 4G 才稳定。教训是集群节点内存要留足余量至少是数据量的两倍以上。第三个坑是时间不同步导致误判下线。有台虚拟机的时间比其它机器慢了几分钟结果集群频繁认为它心跳超时反复触发故障转移日志里全是节点上下线记录。用时间同步服务校准后问题消失。这个坑最难查因为 Redis 本身不报时间错误只能通过观察故障转移频率来发现。6. 集群运维的日常操作与扩展思路6.1 日常巡检该看哪些指标集群上线后日常巡检主要盯几个指标。cluster info里的cluster_state必须是 okcluster_slots_assigned必须是 16384这两个是红线。cluster nodes里不能有节点长期处于 fail 或 handshake 状态。每个节点的used_memory和maxmemory比值要关注接近 1 就要考虑扩容或清理。connected_clients突增可能是有客户端连接泄漏。instantaneous_ops_per_sec反映实时 QPS结合keyspace_hits和keyspace_misses算命中率命中率骤降要查是不是缓存穿透。我习惯写一个巡检脚本定时抓这些指标输出成表格一眼就能看出异常。比一个个节点手动敲命令高效得多也避免遗漏。6.2 扩容缩容的正确姿势扩容就是加新节点然后迁移槽位。新节点先以空集群模式启动用redis-cli --cluster add-node把它加入集群此时它是主节点但没有槽。然后用reshard从现有主节点迁一部分槽给它。如果要给它配从再用add-node --cluster-slave加从节点。缩容则相反先把要下线节点的槽全部迁走确认它没有槽了再用del-node把它移除。注意移除前一定要确认槽已迁空否则集群会缺槽导致 fail。从节点下线相对简单直接del-node即可不影响槽位。整个过程的核心原则是任何时候都要保证 16384 个槽全覆盖。迁移过程中槽是逐个转移的中间状态也是全覆盖的所以在线扩容是安全的。但操作要慢一步步确认别图快。6.3 后续可以深入的方向这套虚拟机集群跑通后你可以往几个方向深入。一是客户端接入用 Jedis、Lettuce 或者各语言的 Redis 客户端连集群理解客户端是怎么做槽位缓存和重定向的。二是监控告警接入 Prometheus 加 Grafana把集群指标可视化设置阈值告警。三是压测用 redis-benchmark 或者自定义压测工具测出集群的吞吐上限和瓶颈点。四是持久化与备份策略研究 AOF 重写、RDB 快照、主从备份的组合方案。我个人觉得最有价值的是把故障模拟做透。生产环境的故障千奇百怪你在虚拟机里能模拟的越多真出问题时就越从容。比如模拟磁盘满、模拟 CPU 打满、模拟网络延迟抖动观察集群在这些极端情况下的表现比看十篇文档都管用。这套环境搭一次能反复用很久性价比极高。