
说实话早年我对Redis 高可用的理解也挺天真的。搭个主从复制再挂一个哨兵就觉得稳如泰山了。直到有一次机房物理节点整机重启Redis 哨兵和业务服务部署在同一台机器上一起失联那 20 分钟的告警轰炸和业务堆积才让我真正意识到高可用架构设计的核心从来不是把数据备份一份而是想清楚故障发生时系统到底还能不能对外服务、数据到底丢不丢、恢复需要多久。如果你正打算做 Redis Cluster 高可用架构设计或者已经在用 Cluster 模式但心里没底这篇文章值得你花点时间看完。我尽量不讲太多教科书式废话全部基于实际搭建和运维经验展开先理清选型逻辑再拆解 Cluster 高可用的底层机制然后给出完整可复现的部署验证流程最后把我踩过的坑一次性倒给你。你可以按章节顺序看也可以直接跳到你现在遇到的问题。1. 高可用不等于备份先想清楚要解决什么问题1.1 主从复制模式解决不了的三件事很多人对 Redis 高可用的理解就停留在主从复制。确实Redis 的主从复制让数据有了多份拷贝主节点挂了从节点保底还有一份数据。但你冷静想一下如果主节点真的宕机了从节点不会自己上位。你得手动登录服务器执行 SLAVEOF NO ONE再把客户端切到新节点这个过程里服务是中断的。就算你加班速度很快10 分钟完成了切换这 10 分钟对线上业务来说已经是事故了。所以主从复制的本质是数据冗余不是高可用。它解决不了三个问题第一故障转移依赖人工介入没有自动恢复能力第二从节点只承担读流量写入仍然集中在单个主节点写能力无法水平扩展第三数据量大到超出单机内存时主从模式没有分片能力只能分区或者上大容量机器成本直线上升。1.2 哨兵模式与原生 Cluster 模式的边界划分哨兵模式解决了第一个问题。Sentinel 进程会持续监控主从节点的健康状态主节点挂了之后它会自动在从节点中选一个提升为新的主节点并且把变更通知给客户端。这确实是高可用但它仍然绕不开单主节点写能力上限这个物理边界。也就是说哨兵模式适合数据量中等、QPS 中等、追求自动切换能力的场景。而原生 Redis Cluster 是另一个维度。它从设计之初就做了两件事一是把数据按照槽位分散到多个主节点每个主节点都可以独立读写从架构层面解决水平扩展二是为主节点配置从节点主节点故障时对应的从节点自动晋升内置了自动故障转移能力。所以如果你的数据规模未来可能超过单机内存几十 GB或者写入 QPS 已经压到单个节点吃紧Cluster 模式更合适。1.3 我对架构选型的基本判断标准我自己做选型时一般看三条标准。第一数据总量有没有可能在一年内超过可用内存的 60%超过就直接考虑 Cluster第二写 QPS 要不要扩展到单节点数倍Cluster 的分片机制天然支持第三无论什么方案都要能容忍跨机架甚至跨可用区的单点故障这个在搭建时就考虑进去不要后面再改。补充一个容易忽略的点如果你用云厂商的 Redis 集群产品也不等于完全不用管架构了。云产品帮你解决了部署和探活但你仍然需要确认跨可用区容灾、从节点只在本地机房、自动故障转移的时长阈值这些配置直接决定了故障时的表现。2. 原生 Redis Cluster 的高可用核心机制拆解2.1 16384 个槽位数据分片的最小单位Redis Cluster 的数据分片逻辑用一个词就能理解槽位。整个集群预定义了 16384 个哈希槽每个 key 通过 CRC16(key) mod 16384 计算出一个槽位然后由集群将槽位映射到具体的某个主节点。槽位可以理解为虚拟桶一个主节点可以负责不连续的多个槽位区间Data 自动分布到不同节点。为什么是 16384 这个数而不是 65536很多资料没讲清楚。节点间心跳包中必须携带本节点负责的槽位信息用 Bitmap 表示的话16384 个槽位占用 2048 字节2KB65536 个槽位则占用 8192 字节8KB。Gossip 消息会随着心跳频繁传播数据量越小传播效率越高。同时 Redis 实例数量一般不会超过 1000 个16384 的粒度已经完全够均匀把槽位数翻四倍只会白白增加网络开销。理解槽位机制对高可用设计非常重要。因为故障转移的粒度本质是槽位主节点挂了它负责的那一段槽位必须由它的从节点接管集群才能保持全量在线。如果某个槽位的主从节点同时挂了集群会进入不可服务状态这正是高可用架构中最需要避免的情况。2.2 Gossip 协议节点之间如何感知彼此状态Cluster 节点之间不是靠中心化的注册中心来同步状态而是走 Gossip最终一致性协议。每个节点维护一份集群状态的全量视图包括槽位分布、节点状态、副本信息等然后每秒在集群中随机挑选一部分节点发起 PING/PONG 消息交换彼此的视图。这个机制的效果是任何一个节点的状态变化经过几轮消息传播最终会被集群中所有节点感知到。Gossip 的好处是去中心化、扩展性好不需要独立的协调者。但代价是状态同步不是实时的存在一个传播窗口期。实际运维中你会发现某个节点刚刚挂掉集群未必立刻感知需要等到 PING 超时这个超时时间就是关键参数 cluster-node-timeout。这个参数设置太短网络抖动就会频繁误判设置太长故障转移迟迟不触发。生产环境一般建议从 10 秒到 15 秒之间起步。2.3 故障转移的完整触发链路Cluster 的故障转移分两步走。第一步主节点 A 的从节点发现向 A 发送 PING 在 cluster-node-timeout 内没有收到 PONG它会将 A 标记为 PFAIL主观下线。标记 PFAIL 之后该从节点会通过 Gossip 消息把 A 疑似下线的信息传播出去。第二步集群中超过一半的主节点也都认为 A 是 PFAILA 才会被标记为 FAIL客观下线此时 A 的从节点可以发起选举。选举过程类似 Raft 的过半数机制。A 的每个从节点都有机会参与竞选优先选择复制偏移量最大的从节点数据最接近主节点该从节点向其他主节点广播请求投票获得超过半数的票数后晋升为新的主节点。晋升成功后它会宣布接管 A 负责的所有槽位并且开始接受客户端对这些槽位的读写请求集群状态重新变为 ok。这段流程里隐藏着一个非常重要的设计客观下线需要超过半数主节点确认。也就是说如果在一次网络分区中某个主节点及其从节点被隔离在少数派分区少数派分区内的节点无法凑齐半数主节点确认就不会触发故障转移避免了一部分节点在分区期间独立对外提供不一致数据。这本质上是一种防止脑裂的保护机制。2.4 脑裂问题与 quorum 机制脑裂是分布式系统最怕的问题同一个逻辑节点实际上有两部分进程都认为自己是可用方各自接受写入数据在两边各自为政恢复时互相冲突。Redis Cluster 通过 quorum 机制在一定程度上压制了脑裂但并没有完全消除。你只要记住一个原则写入必须落在多数派侧。如果集群有 3 个主节点网络分区把 3 个主节点分成 21少数派那一侧的 1 个主节点即使客户端还能连通它它也不会被选举为新的主节点而且当它发现自己无法与其他多数节点通信时会拒绝写入请求或者等待恢复。生产上如果你发现集群在某次分区后出现短时间写入失败不要急着怪 Redis这恰恰是多数派保护在起作用。但要注意Cluster 模式对脑裂的兜底不是百分百的。如果你在少数派侧设置了 allow-while-loading 或者在客户端层面绕过 Cluster 协议直连单节点数据冲突仍然可能发生。架构上建议在客户端侧就只连接集群中的多个节点不要绕过协议做非标准操作。3. 高可用集群的落地从部署到验证的完整流程3.1 环境规划节点数量、端口、实例分布一个可以上线的 Redis Cluster 高可用集群我建议至少 6 个节点3 主 3 从。为什么至少 3 主因为主节点必须超过半数才能投票确认故障2 主集群中一个主节点挂了剩余 1 个无法构成多数等于集群废了。3 主集群中挂掉 1 个剩下 2 个可以投票能正常完成故障转移。节点分配不能只看数量更要看物理分布。最忌讳的做法是6 个 Redis 实例全部部署在同一台物理机上或者分布在同一机架的几台机器上。这样一旦物理机宕掉或者机架断电所有实例同时失效集群高可用等于零。我的建议是主节点和它的从节点必须跨机架分布如果条件允许甚至可以跨可用区比如主节点在可用区 A对应从节点在可用区 B。端口规划上Redis Cluster 每个节点会有两个端口一个对外服务端口如 6379另一个是集群总线端口默认 10000即 16379。集群总线专门用于节点间 Gossip 通信和数据迁移网络安全组和防火墙必须放通这两个端口否则会出现节点间互相不可达、集群状态异常的问题。3.2 搭建 Cluster 的具体步骤前置条件Linux 服务器、Redis 6.x 或更高版本我建议 6.2 以上关闭透明大页并设置适当的 maxmemory。以下是 6 个节点在同一台机器上模拟搭建时的核心配置模板实际生产环境中每台服务器应单独配置。# 在每台服务器的 redis.conf 中开启 Cluster 配置 port 6379 cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 10000 appendonly yes appendfsync everysec requirepass YourStrongPassword masterauth YourStrongPassword # 注意如果用容器部署需要显式声明 cluster-announce-ip 和 cluster-announce-port所有节点启动完成后用 redis-cli 来创建集群并指定主从关系。下面的命令让 6 个节点自动分配成 3 主 3 从redis-cli -a YourStrongPassword --cluster create \ 192.168.1.11:6379 192.168.1.12:6379 192.168.1.13:6379 \ 192.168.1.21:6379 192.168.1.22:6379 192.168.1.23:6379 \ --cluster-replicas 1执行后redis-cli 会打印一份槽位分配计划询问你确认输入 yes 即可。确认后工具会自动完成槽位分配并建立主从复制关系。创建完成后登录任意节点执行 redis-cli -c -a YourStrongPassword cluster info看到 cluster_state:ok 且 cluster_slots_assigned:16384就说明集群基本就绪。有个细节创建集群时的-a参数和配置文件里的 requirepass/masterauth 必须一致否则节点间复制和 Gossip 通信会因为认证失败而异常表现为cluster_state:fail或者反复出现 NOAUTH Authentication required。3.3 高可用验证kill 掉主节点观察故障转移集群搭建成功只是起点高可用架构真正要验证的是故障发生后系统能否自动恢复以及恢复耗时是多少。我强烈建议你上线前做一次完整的故障演练模拟真实宕机场景。首先用redis-cli -c -a YourStrongPassword cluster nodes找出某个主节点的实例 ID。然后模拟宕机可以直接 kill 掉该主节点的进程比如redis-cli -p 6380 -a YourStrongPassword shutdown nosave紧接着每秒执行一次集群状态查询while true; do redis-cli -c -a YourStrongPassword cluster info | grep cluster_state; sleep 1; done在正常情况下你会看到集群状态先变成 fail持续几秒到十几秒等从节点完成选举并晋升后cluster_state 重新变成 ok。这段时间就是故障转移时间它取决于 cluster-node-timeout 和选举耗时。我的实测经验是当 cluster-node-timeout 设为 10 秒时从节点在约 12~15 秒内完成晋升整体业务中断不超过 20 秒。这个验证还有一个目的检查从节点晋升后它是否完整接替了原主节点的槽位。你可以再次执行 cluster nodes看到新主节点带着相同的槽位区间信息上线而不是出现槽位无人认领的情况。如果出现cluster_state:fail且槽位仍 has no slot就要检查集群总线和从节点的复制状态是否正常。3.4 客户端侧的高可用配置集群不可用时的重试与兜底Cluster 部署完成后客户端连接方式必须从普通模式切换到集群模式否则直接连上的只是某一个节点。以 Java 生态中最常用的 Lettuce 和 Jedis 为例客户端要开启集群感知能力让它能够处理 MOVED 重定向和 ASK 重定向并自动感知拓扑变化。更关键的是重试策略与兜底。主节点故障切换期间客户端访问旧主节点的槽位可能短暂失败。为了不让业务调用直接报错客户端层要配置合理的重试次数和退避策略。比如 Lettuce 可以配置 topologyRefreshPeriod 为 30 秒到 60 秒让客户端及时感知到槽位迁移每次操作的 timeout 设为 3 秒到 5 秒重试 2 次避免在故障窗口期内无限等待。另外提醒一个业务架构层面的兜底Redis 集群高可用不代表你的应用就百分百可用。如果 Redis 集群刚好在业务高峰期切换而切换期间有大量请求直接访问 Redis 失败你的服务需要有自己的熔断/降级策略比如把热点非核心数据降级到本地缓存或者返回默认值避免上游请求全部阻塞在等待 Redis 响应上。4. 我在生产环境踩过的坑故障转移比想象中更苛刻4.1 坑一所有主节点全部部署在同一台物理机上这是我见过最典型的假高可用。当时有个业务组把 6 个实例全部部署在同一台 96 核的大机器上理由是这台机器配置高不容易挂。结果机器在半夜硬件告警重启整个集群所有节点同时失联高可用策略形同虚设。从高可用设计角度节点分布密度比单机规格更重要。我的底线是同一主机的 Redis 实例数量不能超过节点总数的三分之一且主从必须打散到不同主机。甚至同一机架也要考虑机架交换机故障会同时干掉一整排服务器。4.2 坑二cluster-node-timeout 设置过大的连锁反应默认的 cluster-node-timeout 是 15000 毫秒如果你不调整主节点故障后要等 15 秒才会被标记为 PFAIL然后还要再花几十毫秒到几百毫秒完成选举总的业务中断时间往往接近 20 秒到 30 秒。对很多线上业务来说这样的中断不可接受。我遇到过另一类问题某团队把 timeout 调到 3 秒结果一次网络抖动导致集群频繁误判主节点切换了好几次每次切换都带着短暂的写入失败搞得业务方天天告警。调这个参数要结合网络环境内网稳定环境建议 5~10 秒跨公网或跨可用区的长链路抖动可能较大建议 15 秒起步。关键是你要知道自己业务能容忍多长的 Redis 中断时间然后反推参数配置。4.3 坑三从节点复制延迟导致的数据丢失Redis Cluster 的故障转移从节点晋升为主节点时只能继承它已经复制的数据。如果主节点故障前从节点复制存在 1 秒的延迟那么主节点最后 1 秒的写入就会丢失。这在金融、交易类场景里是不可接受的。缓解方法有两个。一个是客户端侧尽量减少单线程写入瓶颈降低复制延迟另一个是配置 Redis 的同步保护机制min-replicas-to-write 1 min-replicas-max-lag 10这两行配置的含义是当前主节点至少要有 1 个从节点且从节点的复制延迟不超过 10 秒主节点才接受写入请求。这样一旦从节点故障或复制严重滞后主节点会拒绝写入宁可牺牲可用性也要避免主节点单飞后丢失大量数据。这个取舍你要根据业务决定如果是缓存场景丢了可以回源没必要开如果是持久化数据建议开启。4.4 坑四未使用 hash tag 导致的批量操作失败Redis Cluster 要求客户端必须围绕槽位访问跨 slot 的 mget 命令默认会报错。很多团队第一次迁移到 Cluster 时业务代码里大量使用 mget、pipeline 操作一对 key结果全部失败。解法是合理规划 key 的命名使用 hash tag 语法让一组 key 落在同一个槽位。比如{order:1001}:info、{order:1001}:items这种格式Redis 只对花括号内的内容计算 CRC16因此两个 key 会被映射到同一节点mget 和 pipeline 就可以正常工作。但 hash tag 也不能滥用。如果你把大量 key 的 tag 都设置为同一个值比如{user}:name、{user}:age这些 key 会全部落到同一个槽位直接造成数据倾斜把一个节点打满其他节点闲置。我用过一个相对合理的策略tag 粒度尽量细按业务实例 ID 或订单号分组不要按全局类型分组。4.5 坑五客户端拓扑刷新过慢请求一直被路由到旧节点集群完成故障转移后原主节点对应的槽位被从节点接管。但客户端的路由表并不会实时更新。如果你用 Jedis 且没有开启拓扑刷新客户端仍会把请求发送到已经宕机的旧节点连续报错直到重启客户端。现在我一般建议用 Lettuce 或者 Redisson它们支持周期性刷新集群拓扑。实际配置时把刷新周期设为 30 秒左右并且开启自适应拓扑刷新。这个刷新周期虽然不会让每一次故障转移都做到秒级感知但至少能保证在几十秒内客户端恢复到正确路由配合重试机制业务基本无感。5. 高可用设计的进阶路径从能跑到稳5.1 Cluster 与你到底还需要不需要哨兵这是一个高频问题我都用了 Redis Cluster还要不要单独部署 Sentinel直接结论是不需要。Cluster 模式内部已经实现了主节点故障检测、自动故障转移和配置同步机制哨兵是为主从复制模式设计的组件。如果给 Cluster 再加上哨兵反而会造成状态管理的混乱比如哨兵可能投票选出一个 Cluster 没承认的主节点引发集群状态不一致。另一种情况是你并没有用 Cluster而是仍然采用传统主从复制此时才需要 Sentinel 负责自动切换。我认为决策可以这样简化为单主多从 哨兵模式适合数据量较小、希望成本低、切换自动化运维在可控范围内的场景数据量超过单机内存或写能力已经到瓶颈就直接切 Cluster。两者不是叠加关系而是按场景二选一。5.2 容量规划与性能考量Cluster 的横向扩展能力强但不意味着你可以在扩容上很随意。我一般按这个步骤做容量规划先估算 Redis 最大数据量包括 key 本身、value 对象、过期时间未清的冗余数据和缓冲区再将数据量除以单节点允许使用的内存上限。单节点 used_memory 建议控制在可用内存的 50% 以内给 AOF 缓冲区、BGSAVE 子进程和系统 page cache 留足余地。举个例子假设业务峰值数据量约 60GB单实例内存上限按 16GB 规划至少需要 4 个主节点分片。再考虑主从比例为 1:1那就是 8 个节点。槽位分配时尽量让每个主节点承担约 4096 个槽位数据分布理论上会趋向均衡。扩容时按槽位迁移的方式逐个迁槽建议每次迁移的 slot 数据量不要超过总数据量的 10%否则迁移期间网络负载会明显增高。性能层面一个容易被忽略的细节是Cluster 模式下跨节点操作是很昂贵的。应尽量在 key 设计阶段就把需要批量操作的 key 聚到同一 slot减少跨 slot 的网络往返。另外冷热数据不区分会浪费节点资源如果某些分片访问量异常高你同样可以用 hash tag 做冷热分离让热点 key 分布到更多分片。5.3 监控、告警与定期演练高可用架构不是搭完就结束的上线后的监控与持续验证才是保障真实可用性的关键。我在生产环境至少会盯这几个指标cluster_state、cluster_known_nodes、cluster_slots_assigned、cluster_size任何一个出现异常都要立刻告警。cluster_state不等于 ok 的时候说明已经有部分槽位不可用客户端请求会失败这个必须告警。节点层面的指标同样重要比如主节点的used_memory、mem_fragmentation_ratio、复制积压缓冲区的偏移量master_repl_offset 与 replica_repl_offset 的差值以及主从切换事件日志。延迟监控方面可以在客户端侧记录 Redis 调用的 P99 延迟而不是只看info commandstats。定期演练是很多人忽视的环节。我强烈建议至少每季度做一次故障切换演练专门挑一个低峰时段kill 一个主节点验证整个链路能否自动恢复。演练的收益有两个一是确认配置和代码在真实故障下依然有效二是让运维团队熟记应急流程不要等真正事故发生时才发现某个端口没放通、某个客户端没有配拓扑刷新。我在实际运维中最深的一点体会是高可用架构不是一套配置而是一套持续验证的机制。每次版本升级、参数调整、机房变更都可能悄悄改变高可用的最终效果。你只有在日常中不断模拟故障、验证链路、核对配置才敢在业务高峰期对团队说一句这次事故影响应该可控。如果你之前只是搭过主从复制现在刚接触 Cluster建议先从 3 主 3 从的最小集群开始按我上面提到的流程完整搭建、验证、演练一遍再逐步增加容量和相关配置。等你在测试环境把那些坑都踩过了生产环境自然就稳了。