Redis 这话题聊到高可用基本绕不开主从复制和 Cluster 这两个坎。我见过太多人把这两者混为一谈以为 Cluster 就是多搞几个主从节点摆在那里其实这个理解偏差得挺离谱。恰好前几天在梳理高可用升级路线时又把这套东西从头捋了一遍趁热把 Redis Cluster 和主从复制的本质区别掰开揉碎讲讲尤其是那些从主从模式迁移到 Cluster 时最容易踩的坑一并交代清楚。1. 主从复制到底解决了什么问题先说主从复制。很多人把它理解成数据的多份备份这个说法不算错但太表面了。主从复制的核心价值其实只有一个读写分离和故障时的数据冗余它本身并不提供自动故障转移能力。你部署一主一从主节点负责写从节点负责读数据通过 RDB 快照和增量命令传播两种方式同步到从节点。这里的同步链路本质上是异步的主节点写成功就返回客户端不管从节点到底收到没收到数据。这个异步特性是理解和主从复制相关一切问题的根。举个例子主节点刚处理完一条SET key value还没来得及把这条命令传给从节点主节点直接宕机了。此时从节点上的数据是旧的如果你手动把业务切到从节点上这条数据就丢了。更麻烦的是如果这时候主节点又恢复了网络一重连从节点发现自己的数据比主节点旧会触发全量重同步直接把自己的数据覆盖掉——如果你在从节点上临时写过数据比如为了救急把业务切过来这些数据也会被冲掉。所以主从复制解决的是读流量可以水平扩展和主节点挂了有备用数据这两个问题但它天生需要配合另一套机制来完成故障转移也就是哨兵。我之前在文章里提过多次哨兵是独立于 Redis 之外的一组进程它监控主从的健康状态谁挂了就发起投票选一个新主。这就引出主从复制的第一个本质特征它把数据备份和故障发现/切换分成了两套独立系统。数据层面靠的是 Redis 主从之间的复制协议故障层面靠的是哨兵进程的监控与投票。这种分离设计在内部看似合理但在实际运维中却要维护两套东西而且哨兵对网络分区的反应异常敏感误判场景不在少数。2. 主从复制高可用的隐藏成本如果说主从复制加哨兵是高可用的入门方案那这个方案的隐藏成本很多人其实没算清楚。2.1 哨兵不是万能的它有自己的脑裂窗口哨兵判断主节点挂掉需要两个条件主观下线sentinel 自己发现联系不上主节点和客观下线足够数量的哨兵都认为主节点挂了。这个过程消耗的时间可能在秒级而在这些秒内主节点可能还在接受写入。更经典的问题是脑裂主节点本身没挂只是和哨兵之间的网络抖动此时哨兵可能选出一个从节点提升为新主。旧主恢复后它不知道自己的王位已经被夺了还在傻傻地接收写入。网络分区恢复后旧主发现自己已经变成从节点会做一次全量同步把分区期间所有新的写入全部丢弃。用 Redis 官方的话说这个问题靠配置min-replicas-to-write来缓解但这个参数设高了会影响可用性设低了防不住数据丢失本身就是个两难取舍。2.2 全量同步的放大效应主从复制的另一个隐性成本是全量重同步。只要从节点的复制积压缓冲区大小配得不够或者网络断连时间超过了可以增量同步的窗口从节点就会退化成全量重同步主节点 fork 子进程打一份 RDB传到从节点从节点清空自己的数据再全量加载。这段流程中主节点的 fork 操作会占用系统内存RDB 传输会占满带宽。如果你的实例数据量在 10GB 以上一次全量同步的时间可能长达数分钟期间业务读请求打到这台正在加载 RDB 的从节点上延迟会高到你怀疑人生。这也是很多团队明明做了主从架构线上还是经常出问题的核心原因——不是架构不对而是对全量同步风暴的预估远远不足。2.3 主从模式下所有写流量仍然压在单个节点上主从复制无论挂多少从节点写请求只能打到主节点上。你的从节点数量从 1 加到 10对写能力的提升等于零。真正有写瓶颈每秒几万到十几万写入的业务主从模式迟早会撞上单机上限这时候你需要的不是更多从节点而是把数据拆开分散到多个节点上——这就是 Cluster 登场的时机。3. Cluster 的分片本质数据不再人人有份Redis Cluster 和主从复制最根本的区别从我对这两个架构的理解来看可以概括为一句话Cluster 是从数据组织方式上彻底重构而不是简单地把多个主从节点串在一起。Redis Cluster 把整个 keyspace 分成 16384 个哈希槽。写入一个 key 时先对 key 做 CRC16 校验再对 16384 取模得出这个 key 属于哪个槽。集群中的每个主节点负责其中一段连续或不连续的槽位区间。这里有三个和主从复制截然不同的点很多人一开始都转不过弯来数据分布的确定性。同一个 key 永远只会落到同一个槽位由固定的节点提供服务。而在主从复制架构里所有 key 都在每个节点上有一份完整拷贝任意从节点都能响应读请求。一个是每份数据只在特定节点上另一个是每份数据在所有节点上都有这个差异直接决定了后续所有行为的不同。路由逻辑前置。客户端往 Cluster 任一节点发请求时如果 key 对应的槽位不在该节点上节点会返回MOVED错误把正确节点的地址告诉客户端。新版本的 redis-py、Lettuce、Jedis 等客户端都会自动处理这个重定向。这就意味着你需要维护一套槽位→节点的映射表而主从复制模式下完全不存在这个问题。多主多写。Cluster 支持在多个主节点上同时写入每个主节点都承载写流量的一部分。主从复制只能一个主节点写这是两个架构能力差距最大的地方。关于槽位分布再补充一个实操中容易忽略的点Cluster 创建时槽位是平均分配的你也可以用cluster addslots手动分配不均匀的槽位但数据的分布完全取决于你的 key 的 CRC16 结果分布是否均匀。如果你把大量固定前缀的 key 放进去比如user:10000到user:20000这些 key 的 CRC16 取模后可能大量落在少部分槽上导致一部分节点的数据量远高于其他节点。遇到这种情况要么引入 hash tag 把相关 key 强制放到同一槽要么让业务层做好拆分再做路由。4. Cluster 的故障转移与主从复制在路径上的根本分歧聊故障转移最能看出两个架构站的角度完全不同。主从复制的故障转移是外置式的哨兵独立部署通过 PING/PONG 监控 Redis 进程检测到主节点不可达后哨兵集群内部发起 Raft 投票选出一个 leader 哨兵由它执行主从切换。整个切换过程和 Redis 自身的节点间通信没有直接关系——Redis 节点只被动接受SLAVEOFRedis 5 之后是REPLICAOF命令来调整自己的主从关系。Cluster 的故障转移是内置式的节点之间通过 Gossip 协议持续交换状态信息每个节点都保存着整个集群的节点列表和槽位分布。当一个主节点被集群内过半数的节点标记为不可达FAIL并且它的从节点监测到主节点失联超过cluster-node-timeout时间后从节点会发起选举向集群内所有主节点请求投票获得多数票后提升自己为新主并接管旧主的全部槽位。这个过程里有两个细节特别能体现两者的气质差异一是故障感知的分布式程度。哨兵模式下故障判断依赖哨兵集群这个旁路系统哨兵集群自身也是需要部署和高可用的组件。Cluster 模式下没有独立的旁路系统故障感知和切换都由 Redis 节点自身完成架构组件少了一个层级。当然代价是节点间的 Gossip 通信本身要占用一定的带宽和 CPU节点数量越多状态传播的延迟就越明显。二是从节点提升的触发条件。哨兵模式的切换触发完全由哨兵监控结果决定Redis 节点自己不感知也不需要感知自己在被切换。Cluster 模式下从节点能否晋升为主节点取决于它接收到的复制偏移量是不是最接近原主节点太落后的从节点在选举中天然处于劣势。这种最接近最新数据者优先的机制类似 Raft 里的 up-to-date 限制本身就比哨兵模式更贴近数据一致性的考校。不过 Cluster 的故障转移在两类场景下会遇到麻烦——这是我实测中踩过的比较深的水第一类是槽位迁移期间的故障。如果正在把一个主节点的槽位迁移到另一个主节点此时源节点宕机槽位状态是MIGRATING目标节点状态是IMPORTING客户端可能同时收到MOVED和ASK两种重定向。如果集群的故障转移恰好在这个窗口触发新主接管槽位后那些正在迁移中的数据可能会处在一个中间状态旧主的数据没完全搬完新主以为槽位已经接管了。好在 Redis 的迁移流程本身设计得比较健壮故障触发后会回滚未完成的迁移但这个过程在槽位迁移量极大比如分布式缓存治理中常见的大规模 reshard时会拖得很长。第二类是网络分区下的脑裂问题。Cluster 同样有这个问题而且因为节点之间本身就是 Gossip 通信网络分区时两侧节点各自认为对方挂了每一侧都可能产生新的主节点。从节点发起选举时会先等待一个随机延迟DELAY一般是 0 到 1 秒来降低多个从节点同时发起选举的概率但网络分区造成脑裂时旧主恢复后发现自己被降级为从节点同样会触发全量同步丢弃数据。这和主从复制下的脑裂形态不同但风险是客观存在的。5. 数据一致性模型两套架构的最终分野如果只让我选一个最能体现本质区别的角度我选数据一致性模型。你可以通过查官方文档看到主从复制的同步是异步的所以从节点在任意时刻都可能落后于主节点。你读从节点时读到的数据多少是过期的这个过期窗口取决于网络延迟、从节点负载、是否发生全量重同步等因素。这是很多业务的缓存读取场景可以接受的但对强一致有要求的业务就属于致命缺陷了。Cluster 在这个问题上并没有根本性改进。单个 slot 内Master 向它的 Replica 传播写操作同样是异步复制也就是说 Cluster 模式下写入某主节点后立刻读它的从节点同样可能读不到刚写入的数据。唯一不同的是Cluster 通过 key 的槽位固定性保证了如果你总是直接访问管理该槽位的主节点那数据必然是最新的——但一旦你开启了从节点读请求READONLY命令 客户端侧的从节点优先策略或者主节点挂了切换到了从节点这个保证就不存在了。所以一个有意思的结论是Cluster 并不是比主从复制更一致的架构它只是把一致性问题的范围从整个 keyspace缩小到了单个槽位同时让你可以通过控制请求路由来决定到底接触哪个节点。这种控制粒度是主从复制给不了的。我记得在一次压测中对比过两者的表现主从架构从节点读取的延迟均值和 P99 与主节点有明显差值而 Cluster 模式下因为客户端会把 key 直接路由到对应的主节点读请求打到主节点上的延迟反而更低。这个现象其实很好解释——Cluster 下数据分散后单节点数据量更小RDB 快照和 AOF 的写入成本降低加上 Redis 单线程模型对 CPU 缓存的友好度提升整体延迟反而比所有数据压在单主节点多从节点同步的模式更好。6. Cluster 单分片内的主从复制不是二选一而是组合看到这里有人可能会问那 Cluster 模式到底还需要主从复制吗答案是需要。Cluster 实际上是把分片和主从复制两个概念叠在一起用一个 Cluster 由多个分片组成每个分片内部是一个主节点加若干从节点。数据在分片维度上通过槽位相互隔离在分片内部通过主从复制保证冗余和故障转移的候选节点。这也就引出了 Cluster 模式下你配置主从关系的方式与主从模式的不同——配置用的是CLUSTER REPLICATE而不是REPLICAOF。操作序列大概是# 1. 创建 Cluster 时指定从节点数量 redis-cli --cluster create \ node1:6379 node2:6379 node3:6379 \ node4:6379 node5:6379 node6:6379 \ --cluster-replicas 1--cluster-replicas 1的意思就是每个主节点分配 1 个从节点。六个节点会形成三个分片每组一主一从。槽位分配由工具自动完成三个主节点均分 16384 个槽位。如果你已经有 Cluster 在运行想过再手动调整从节点归属可以用# 让 node4 成为 node1 的从节点 redis-cli -p 6379 -c cluster replicate node1_idnode1 的 ID 可以通过cluster nodes拿到。这里有个容易踩的细节一个从节点只能有一个主节点切换主节点时必须先用cluster reset清理旧的复制关系。Cluster 架构下主从复制的同步机制和独立主从架构下完全一样先 RDB 全量同步然后增量命令传播。但是因为每个分片的数据量只是全量数据的一部分全量同步的时间通常远小于单主从架构这算是一个架构分片带来的隐形福利。7. 迁移与选型什么情况下该放弃主从转向 Cluster讲了这么多区别落到实际就是一张决策表。从我的经验看不要因为Cluster 更高级就盲目从主从迁移过来。以下场景还是老老实实用主从加哨兵性价比更高数据量在几十 GB 以内单节点 QPS 完全够用瓶颈主要在读流量这种情况扩从节点更简单。你不需要跨多节点写入希望架构尽量简单运维团队人手不足。你是单机 Redis 刚开始做高可用数据量不大优先把主从哨兵这套基础打稳。反过来以下场景就别犹豫直接上 Cluster数据量预计超过单机内存的合理水位比如超过几十 GB或你不想依赖大内存机器。写流量持续上涨单主节点已经或即将成为瓶颈。你希望故障转移和主从切换由 Redis 自身完成不想在企业内部额外维护一套哨兵集群。你对扩容有明确需求——Cluster 加节点是横向扩容主从加从节点对写能力毫无帮助。迁移过程中有个绕不开的头疼问题从主从架构往 Cluster 迁移时数据搬移不能靠redis-cli --cluster import这种粗暴导入因为它要把数据单线程地 rehash 到新集群时间完全不可控。我当时用的方案是双写迁移先跑一段双写旧主从和新 Cluster 同步写入再灰度切换读流量切换期间补一次增量数据。如果你的业务允许短暂停止写入也可以接受停机时间直接导出 RDB 再导入 Cluster 是最省事的。另外大小心细的提醒Cluster 模式下MULTI事务、SCAN游标、KEYS等命令的行为全变了。KEYS在 Cluster 下只能扫到当前节点上的 key不是全集群的 key经常有人在排查问题时对着单节点的KEYS发呆半天才反应过来。MULTI/EXEC操作多个 key 时这些 key 必须落在同一个槽位否则直接报CROSSSLOT错误。如果你的业务里还有大量跨 key 的事务操作迁移到 Cluster 之前必须把这些操作重新设计比如用 hash tag 或拆成多个原子命令。8. 我在实操中关于 Cluster 和主从复制的几点体会说到底主从复制和 Redis Cluster 没有谁完全替代谁的关系。它们的本质区别在于对数据的组织方式一个把所有数据放在同一个逻辑空间里通过复制来扩展读能力一个把数据切碎后分布在多个逻辑空间里同时扩展读写能力。这两种思路服务于不同的瓶颈点选哪个取决于你的瓶颈到底在读、在写、还是在容量上。我自己在多个项目里反复迁移迭代过最后的经验是高可用架构没有银弹越复杂的架构带来的运维复杂度越高Cluster 虽然解决了单机容量和写瓶颈但它引入的槽位路由、跨槽事务限制、Gossip 网络开销等约束会在你长期维护时不断刷存在感。如果数据量没到那个临界点别提前给团队找罪受真到了需要 Cluster 的阶段尽早把客户端路由逻辑和缓存治理方案设计好避免迁移之后才手忙脚乱地补充那样不止是技术债还是睡眠债。
阅读完成 · 觉得有帮助?