1. 写在前面的几句实话先别急着看配置命令。我在群里见过太多人把“Redis集群”和“Redis主从”“Redis哨兵”混为一谈上来就照着博客敲redis-cli --cluster create结果三个节点搭完发现数据没分片、故障也不会自动切换最后一脸懵地回来问“为什么我的集群主节点挂了写入还是失败了”。这个事得说清楚Redis官方提供的Cluster集群模式解决的从来都不是单机容量不够的问题——它解决的是“数据自动分片 高可用”这一揽子事。单机内存不够你可以开主从、加哨兵但数据总量超出单机物理内存上限之后只有Cluster模式能让你把数据拆到多台机器上同时还能自动感知节点故障并完成主从切换。这篇文章会从最底层的槽位分配机制讲起先把Cluster模式的架构逻辑拆明白然后给一套完整的部署流程最后把我实际运维过程中踩过的坑和排查思路一并整理出来。无论你是刚准备把Redis从单机搬向分布式的开发还是要给生产环境做集群选型评估的运维这篇都值得看完——至少能帮你少走两个月的弯路。2. 三种“集群”方案的本质区别2.1 主从复制模式把数据多抄几份主从复制Master-Slave Replication是最朴素的方案。一个Master节点负责写若干个Slave节点负责读Master把写操作通过异步复制同步到Slave。数据在所有节点上是一模一样的——这不是“集群”这就是“副本”。它的直接问题是存储容量受限于单个Master节点的内存上限Master挂了需要人工干预或者引入哨兵才能把Slave提升为新Master所有写入压力全部压在一个节点上横向扩容等于没扩我见过不少团队用主从当集群对外宣称“我们上了Redis集群”实际上连个自动故障转移都没配。一问就是“我们数据量不大够用”。对对于数据总量远小于单机内存的场景主从方案完全够用而且成本最低。但这不是本文要聊的“集群”。2.2 哨兵模式给主从加个“自动监理”哨兵模式Sentinel说白了就是在主从复制的基础上加了一套监控和故障转移机制。Sentinel进程负责监控Master和Slave的健康状态当Master不可达时Sentinel会投票选举出一个新的Master然后通知所有客户端和Slave切换连接。这个方案解决了高可用问题但存储容量仍然有上限——因为哨兵模式本质上还是主从数据没有分片每一份数据都完整保存在每个节点上。你在主从架构上加了哨兵得到的是“高可用”不是“可扩展”。2.3 Cluster模式数据分片 自动故障转移Cluster模式才是真正意义上的分布式集群。它把整个数据空间划分为16384个固定槽位slot每个Master节点负责一部分槽位。任何一个key经过CRC16(key) % 16384计算落到对应的槽位然后路由到负责该槽位的节点。写入一个key时客户端算出的槽位如果不在当前连接的节点上节点会返回一个MOVED错误并告知客户端正确的节点地址客户端再跳转过去执行操作。不依赖任何独立的代理层——Cluster模式下客户端需要自己实现槽位路由逻辑这是和很多“中间件代理型集群方案”最大的区别。三种方案放到一起对比就非常直观了方案数据分片自动故障转移容量扩展性复杂度适用场景主从复制不支持全量复制不支持需人工无低读写分离数据量小哨兵模式不支持全量复制支持无中对高可用要求高但数据总量不大Cluster模式支持16384个槽位支持支持在线扩缩容较高大数据量、高吞吐场景我个人的建议是如果数据总量预估未来会超过单台机器内存的七八成直接上Cluster模式别在主从和哨兵上浪费时间。原因很简单——从主从迁移到哨兵还勉强平滑但从哨兵迁移到Cluster数据要重新分片、客户端要改路由逻辑和重新做一个系统没什么区别。3. 核心细节槽位、虚拟节点与数据路由3.1 为什么是16384个槽位Redis官方把整个数据空间划分为16384个槽位这个数字不是拍脑袋定的。设计者antirez在源码注释和社区讨论里解释过16384个槽位足够保证数据分布均匀同时槽位信息在节点间传播的代价是可控的。这里有具体的计算逻辑。集群里的每个Master节点都需要向其他所有节点广播自己负责的槽位信息。如果槽位数量是N那么一条心跳消息中用于描述槽位信息的bitmap大小就是N/8字节。16384/8 2048字节也就是2KB。如果槽位增加到65536个那每个心跳消息就要携带8KB的槽位信息网络开销直接翻四倍。而实际情况中超过1000个节点的集群本来就十分罕见16384个槽位在数据均匀性上已经足够了——这是典型的在数据分布质量和网络通信开销之间的工程取舍。3.2 数据是怎么落到具体节点的Cluster模式下的数据分布遵循哈希分片原理对key做CRC16校验计算得到一个16位的哈希值对这个值取模16384得到槽位编号根据CLUSTER SLOTS命令返回的槽位映射表找到负责该槽位的节点客户端直接连接该节点执行命令比如我要写入hello这个keyslot CRC16(hello) % 16384算出来的slot是某个数值然后查一下这个slot由哪个Master负责就只往那个节点写。这也是Cluster模式下MGET这类多key操作受到限制的原因——如果多个key不在同一个槽位就不能用一条命令原子性地完成读写。Redis为此提供了hash tag机制key中的{}部分会被单独拿出来做哈希计算比如{user:123}.name和{user:123}.age中user:123部分一样所以这两个key会落到同一个槽位可以被组合操作。3.3 主从节点角色和槽位的关系Cluster模式下每个节点可以扮演三种角色Master负责一部分槽位的读写Slave从属于某个Master做数据备份Master挂了之后顶上空节点不服务任何槽位随时准备加入集群需要注意的是从节点不处理任何读写请求。即使你在客户端指定连接从节点Cluster模式下的从节点也只会把请求转发给对应的Master。这是Cluster模式与主从模式最大的行为差异之一——主从模式下你可以配置slave-read-only no让从节点承担读流量但Cluster模式设计上就没打算让从节点对外服务它的唯一价值是故障转移时的备胎。3.4 客户端路由MOVED与ASK当客户端连接的节点不负责某个key对应的槽位时节点会返回MOVED错误并且在错误信息中附带上正确的节点地址。标准客户端收到MOVED后会更新本地缓存的路由表然后重新请求正确的节点。还有一种ASK错误常见于集群槽位迁移过程中。数据正在从A节点迁往B节点此时请求命中尚未完成的slotA节点会返回ASK错误让客户端去B节点查询。和MOVED的区别在于ASK不会让客户端更新本地缓存的路由表——这只是临时性的“转一次”迁移结束后路由表会彻底变更。这块是最容易踩坑的地方。如果某个Redis客户端不完整支持MOVED和ASK的重定向逻辑一些老旧的客户端库就是这样的那么在集群槽位发生迁移时会出现大量请求报错。所以选型时务必确认客户端库对Cluster模式的原生支持情况别拿个Jedis就去连Cluster集群——Jedis的API虽然支持但你需要手动处理一些细节而Lettuce则原生封装好了重定向逻辑用起来省心得多。4. 实操部署从零搭一个三主三从集群4.1 集群规划与部署方式选择生产环境的Redis Cluster起码要6个节点3主3从。三主负责扛数据写入三个从机分别是这三个主机的备份。如果主机和从机比例低于1:1任何一个主节点故障都会导致该分片数据不可用。你可以用物理机、虚拟机、容器Docker/K8s三种方式部署。容器化部署方便但要注意容器重启后IP变化会导致集群元数据失效。最稳妥的方式是给容器设置固定IP或者使用host网络模式。这里用经典的物理机/虚拟机方式演示因为最直观、最容易被复现。规划如下节点IP地址端口角色node-1192.168.1.1017000Masternode-2192.168.1.1027000Masternode-3192.168.1.1037000Masternode-4192.168.1.1017001Slavenode-5192.168.1.1027001Slavenode-6192.168.1.1037001Slave每个节点上启动两个Redis实例端口分别为7000和7001在同一个物理机上主备错开避免机器故障时主备同时下线。4.2 修改配置文件的几个关键项每台服务器上的redis.conf需要修改以下核心配置# 启用集群模式 cluster-enabled yes # 集群配置文件节点会自动维护这个文件不要手动编辑 cluster-config-file nodes-7000.conf # 节点超时时间单位毫秒超过这个时间节点会被判定为不可达 cluster-node-timeout 15000 # 开启AOF持久化 appendonly yes # 绑定内网IP bind 192.168.1.101 # 守护进程方式运行 daemonize yes # 端口 port 7000 # 集群总线端口Redis会使用port10000作为集群内部通信端口需要放行防火墙 cluster-bus-port 17000注意这里有个特别容易被忽略的细节cluster-bus-port集群总线端口是在你配置的端口基础上加10000得到的。比如7000端口的实例总线端口就是17000。集群节点之间的通信走的是这条总线包括心跳检测、槽位信息广播、故障选举等。如果只放行了业务端口没放行总线端口节点之间会互相看不到对方集群永远无法建立起来。多个实例在同一个物理机上跑时7001端口的实例需要把配置里的端口、集群配置文件名、总线端口号一并对调。配置文件不要用同一个文件去启动两个进程——appendonly文件名、cluster-config-file都会冲突。我见过有人用相同的配置启动了第二个端口结果第二个实例直接把第一个实例的持久化文件给覆盖了大半夜被电话叫起来恢复数据这种低级错误真的一次都不能犯。4.3 启动实例并组建集群先把六份配置准备好然后逐个节点执行redis-server /etc/redis/redis-7000.conf redis-server /etc/redis/redis-7001.conf启动后用redis-cli -p 7000 cluster info检查实例状态。如果返回cluster_state:fail不需要紧张——单个节点在不完整的集群状态下就是fail等所有节点都加入组网后就正常了。组网有两种方式老版本手工用meet命令新版本用redis-cli --cluster create。官方推荐后者redis-cli --cluster create \ 192.168.1.101:7000 192.168.1.102:7000 192.168.1.103:7000 \ 192.168.1.101:7001 192.168.1.102:7001 192.168.1.103:7001 \ --cluster-replicas 1参数说明先列出的三个节点会成为Master节点后面列出的三个节点按顺序会分配给这三个Master做Slave--cluster-replicas 1表示每个Master分配一个Slave执行后脚本会显示一张分配计划表询问你是否同意。核对无误后输入yes确认集群就建成了。这时用redis-cli -c -p 7000 cluster nodes查看到的输出大概长这样292f490e4c7c4d8e1f0f1a5a3b4c5d6e7f8a9b0c 192.168.1.101:700017000 master - 0 1700000000000 0 connected 0-5460注意每个Master都带着一段槽位区间三个Master加起来正好覆盖0–5460、5461–10922、10923–16383合计16384个槽位一个不多一个不少。4.4 验证集群可用性用-c参数连接集群-c表示开启集群模式客户端会自动跟随MOVED重定向redis-cli -c -p 7000127.0.0.1:7000 set hello world OK 127.0.0.1:7000 get hello world如果写入时没有MOVED报错出现说明路由重定向正常。验证slot覆盖情况redis-cli -p 7000 cluster info | grep cluster_slots_assigned输出cluster_slots_assigned:16384说明槽位分配完整。再验证故障转移把其中一个Master杀掉然后观察Slave是否自动顶上。比如杀掉192.168.1.101上的7000实例redis-cli -p 7000 shutdown nosave等大约cluster-node-timeout秒再加上一点余量执行cluster nodes。你会看到192.168.1.101:7001的Slave节点角色从slave变成了master并且它的master状态变成了fail。正常情况下等待十几秒后新Master就会接管原本的slot区间。注意在cluster-node-timeout设置为15000毫秒的情况下故障转移完成需要的时间约等于15秒到30秒。如果你设置了更大的timeout比如60秒业务侧的写入中断时间会更长。生产环境务必要让业务方接受这个故障转移时间窗口别让数据库层以为Redis彻底挂了而直接把流量切走后端数据库。5. 常见问题与排查技巧实录5.1 集群状态fail的排查现象cluster info显示cluster_state:fail写入时报CLUSTERDOWN The cluster is down。第一反应是查cluster nodes输出看是否有节点处于fail状态。如果一个Master不可达并且它的从节点也没有完成故障转移那么整个集群会拒绝写入——Redis集群的默认行为是“fail-stop”确保数据一致性优先。排查路径确认那个节点是否真正宕机还是网络抖动被误判检查集群总线端口是否被防火墙阻断查看不可达节点的日志重点关注cluster-node-timeout相关的告警信息如果节点只是暂时的网络隔离恢复后集群状态会自动变回ok最棘手的情况是某节点素来健康但被活跃的主节点投票标记为PFAIL再升级为FAIL。此时需要确认各节点之间的时钟是否同步建议在所有节点统一配置NTP时钟同步防止因为时钟严重漂移导致选举判断异常。5.2 槽位迁移后客户端写入失败现象集群做了在线扩缩容过程中部分请求报错。原因大都是客户端路由表过期。节点迁移槽位时部分key已经从A节点搬到了B节点如果客户端仍然往A发送请求且A认为自己已经不是该槽的负责者会返回MOVED而不是ASK。支持重定向的客户端会立刻更新缓存向新节点重新请求。排查时用redis-cli -c手动测试一下错乱的几个key正常跳转的话就说明集群本身没有问题问题在客户端库的路由感知不完善。建议升级客户端库到支持Cluster重定向的版本确认连接池的配置不会长时间持有旧连接在扩缩容的低峰期进行操作降低对业务的影响窗口5.3 数据倾斜与热点key治理Cluster模式虽然帮你分了片但分片是否均匀取决于key对于不同槽位的分布是否均匀。我遇到过某个业务的key全部是user:{uid}:profile而{uid}把uid全部哈希到一个槽位上导致单个节点的内存和CPU配额被吃满其他节点闲置。排查时用redis-cli --cluster rebalance查看各节点的key数量分布再结合INFO的used_memory对比各个节点的内存占用。发现倾斜后两种解法在业务侧重新设计key拆掉hash tag让数据自然散落到不同槽位用redis-cli --cluster reshard手动迁移部分slot把热点区域的槽位挪到空闲节点5.4 集群脑裂和数据丢失问题Cluster模式在极端网络分区场景下可能同时出现两个Master上都存在更新的情况。Redis集群靠主从投票机制尽量避免这一点但无法绝对消除。最典型的场景Master和Salve之间网络中断了一段Salve联系其他节点后把自己选举成新Master旧Master恢复连接后发现自己已被降级为从节点于是丢弃掉分区期间的写数据。操作系统的tcp_keepalive时间、内网防火墙以及cluster-node-timeout的数值都和脑裂的概率直接相关。我个人习惯把cluster-node-timeout设置在10秒到15秒之间同时保证内网交换机即时隔离故障网卡大幅缩短故障检测时间把丢数据窗口压缩到最小。另外开启AOF且appendfsync always会比默认的everysec更稳代价是写入性能大幅下降。关键业务如果只有单个Master承载核心写流量建议牺牲一点性能换强落盘保证可以在某个高优场景里有效兜底。5.5 带宽瓶颈与集群总线节点多了以后集群总线的心跳消息、槽位广播、节点状态同步都挤在专门的总线端口中。遇到大规模集群的莫名超时、选举频繁触发等问题先看总线端口的流量占用。如果流量过高可以采取以下手段调大cluster-node-timeout减少不必要的频繁心跳竞争把节点按机房/可用区分组减少跨地域的心跳延迟检查是否存在不必要的频繁槽位挪动它会产生大量增量同步消息把总线带宽打满6. 扩缩容实操速记6.1 扩容新节点加入准备一台空节点启动后在任意现有节点上执行redis-cli --cluster add-node 新节点IP:端口 任意现有节点IP:端口新节点加入后是一个没有任何slot的空Master你需要手动把部分槽位分给它redis-cli --cluster reshard 现有节点IP:端口按提示输入要迁移的槽位数然后输入目标节点ID再输入源节点ID填all表示从所有源节点平均抽取。迁移期间对业务的影响非常小因为使用ASK临时重定向来保证数据的一致性。但要提醒你每次迁移的槽位数不要一次拉太大建议单次200到500个slot方便中途观察各个节点的内存和网络状态有问题随时中断。6.2 缩容节点下线缩容比扩容麻烦一点流程是先把要下线的节点上的slot迁走再用forget命令把节点移出集群。# 1. 迁移需要下线的Master节点的所有slot目标为其他节点 redis-cli --cluster reshard 现有节点IP:端口 # 2. 确认节点已无slot redis-cli -p 待下线端口 cluster nodes | grep 待下线节点ID # 3. 在所有其他节点上执行forget把节点移出集群 redis-cli -p 其他节点端口 cluster forget 待下线节点ID建议按这个顺序操作不要先forget再迁移slot——一旦节点被移除集群它上面的slot会成为孤儿数据其他节点无法访问恢复起来非常麻烦。7. 最后一点个人体会从我维护过的多个Redis集群来看Cluster模式本身的稳定性非常高大部分故障都是“人为事故”配置遗漏了总线端口、客户端路由逻辑不健全、故障转移参数和业务超时不匹配、扩缩容流程操作顺序颠倒……这些坑其实都在文档里写过但文档是死的遇到真实场景时很容易被忽略。我的实操建议是生产环境所有节点统一使用同一份配置模板只改动IP、端口、文件路径这些差异化字段部署完成后做一个完整的混沌演练手动杀Master、杀Slave、断网、迁移slot把可能出现的异常全部演练一遍监控指标至少涵盖cluster_state、cluster_slots_ok、used_memory、total_net_input_bytes这几个关键项。还有一个容易被忽略的小技巧集群的备份不是用SAVE生成RDB去手工复制而是依赖每个节点的从节点。所以从节点的持久化配置绝对不能关否则主节点故障时从节点顶上来的数据可能是不完整的——这是极端场景下数据安全最后的一道防线。Redis Cluster没有那么神秘它的核心也就是“槽位取模 心跳仲裁 故障转移”这三件事。把这套机制吃透了再去配置和排障就有主心骨了。如果你正在规划一套新系统从一开始就把Cluster模式当默认选项来考量后面省下的迁移成本远超你初期多花的学习时间。文末再分享一个压箱底的小操作当Redis节点因为OOM被系统杀掉但日志里没有任何Redis自身的报错时先别急着重启进程——去检查/var/log/messages里Kernel OOM Killer的记录。Redis集群的节点在内存耗尽时会被Kernel强制杀掉这个坑排查起来极其隐蔽我至少有两次线上事故都栽在它上面。如果检查后确认是OOM那么优先优化内存配额和淘汰策略不要盲目重启产能低的旧进程。