
搞技术这行最怕什么凌晨三点电话响客户说Redis挂了网关上一坨一坨的报错。以前单节点Redis确实省心可它一挂下面缓存穿透、数据库被拖垮一套连锁反应全来了。后来自从很多人把Redis高可用拿哨兵和集群来扛这两套方案确实都是“挂了以后能自动切”但原理和适用场景差别非常大。这篇文章就把哨兵和集群两套机制翻个底朝天。我不只是给你列命令而是把主观下线、客观下线、Leader选举、哈希槽、MOVED重定向这些概念讲明白顺便再给你一些生产环境实战经验。它适合这么几类人准备给线上Redis加高可用的工程师、面试前最后冲刺的技术岗同学、以及在哨兵和集群之间反复横跳不知道怎么选的团队。看完不一定保证你能从零搭出一个生产级架构但踩坑会少很多。1. 高可用不是玄学先搞懂复制这套地基1.1 为什么主从复制是必须打好的第一块砖很多人一上来就研究哨兵怎么配置、集群怎么部署结果聊到“全量同步”“部分重同步”一脸懵。其实哨兵和集群的故障转移本质上都依赖主从复制。哨兵把其中一个从节点提升为主节点集群也是让某个副本接棒根子上还是“两个实例之间把数据同步上”这件事。Redis的主从复制有一个非常关键的性质默认是异步的。主节点收到写命令后先自己执行、写内存再把命令发给从节点。这个异步步骤在正常情况下延迟很低几毫秒的事但架构上意味着只要主节点在把命令同步给从节点之前宕机这条数据就算丢了。如果你把Redis当缓存用了丢几条忍忍就过去但如果里面存了未落库的订单锁或计数丢数据就是业务事故。1.2 全量同步和部分重同步两条路差别很大从节点第一次连上主节点或者断线时间太长会走全量同步主节点fork子进程生成RDB快照推给从节点再把缓冲下来的后续命令补过去。如果断线时间短主从可以通过repl-backlog-size配置的重积压缓冲区做部分重同步效率高很多。建议把repl-backlog-size调大一些比如64MB到128MB这样平时网络抖动几秒钟不至于一下掉回全量。全量同步很贵一台内存几十GB的Redis如果反复做全量主节点要fork出子进程把内存打一份快照CPU、磁盘、带宽全部吃紧很容易把业务拖垮。所以主从复制不只是高可用的地基也是性能敏感点配置的时候要提前想清楚。主从复制还会带来一个很现实的问题读取一致性。从节点看到的数据可能比主节点落后一点尤其主节点写入压力大时。做读写分离的朋友要注意刚写完主库立刻去读从库可能读不到。方案是“写后读”这种场景数据走主库或者读从库前确认复制延迟在可接受范围内。这点在哨兵和集群下一样存在。2. 哨兵机制深度拆解让Redis学会自己故障转移2.1 Sentinel到底在做什么为什么要三个所谓“哨兵”就是一组特殊的Redis进程它们盯住master和slave。master挂了Sentinel负责选一个slave顶上这就是自动failover。一个Sentinel扛不住比如它自己所在的机器宕了或者它和master之间的网络被交换机黑洞了没人做决策。所以Sentinel至少要3个在一个“奇数小分队”里互相监督、共同决策。有一点必须说清楚这里的3个Sentinel不是“3台实例装在一起”而是至少部署在不同物理机或可用区里否则一次机房断电就把哨兵和高可用一起带走了。很多人会问要多少个Sentinel才算高可用常规建议是至少3个。如果只有2个Sentinel当其中一个故障剩下的那个在“判定master是否客观下线”时可能永远达不到多数故障转移就卡住了。3个节点的容忍度是挂1个还能跑5个可以容忍2个挂掉但成本更高小型业务用3个足够。2.2 主观下线与客观下线两句话讲清楚Sentinel通过发送PING给master如果master在设置时间内没回复这个Sentinel就认为master“主观下线”sdown, subjective down。注意这只是单个Sentinel看到的状况可能只是网络抖动、主节点堆栈卡顿不一定真挂了。为了不误判Sentinel会把自己的判断告诉其他Sentinel“我觉得master挂了你们怎么看”如果超过quorum数量的Sentinel都认可那么master就被标记为“客观下线”odown, objective down这时候才真正触发故障转移。两个下线概念的区别很重要sdown是“我觉得它不行”odown是“大家都觉得它不行”。生产环境容易踩坑的是把down-after-milliseconds设置得太小比如几百毫秒Redis轻微的GC停顿或网络抖动就会触发误判主备来回切换比不挂还难受。我一般建议至少5秒起步阈值跟着监控告警走别压得太苛刻。2.3 Leader选举故障发生时谁说了算“客观下线”确认后多个Sentinel都能察觉但它们需要选出一个Leader来执行故障转移不能大家一拥而上。这里用的算法带了一点Raft的味道每个Sentinel在尝试发起切换时先给自己投票同时向其他Sentinel拉票。如果某个Sentinel拿到了“多数派”的票它就当选Leader。所谓多数派是多少5个Sentinel就是3票3个就是2票。注意quorum数量只是“判定客观下线”的门槛Leader选举真正的门槛是多数派majority。例如3个Sentinel配上quorum2如果其中1个死了剩下2个仍然可以完成切换如果配quorum1也还是需要多数派才能发起选举。这一点很多人没搞明白面试也爱问记牢。2.4 故障转移完整流程从发现到切换一共七步当Leader Sentinel产生后故障转移大致会这么走过滤掉不健康的候选从节点在master挂了之后还断线超过一定时间通常10倍down-after-milliseconds的、复制偏移量落后太多的、状态异常的都会被划掉。给候选从节点排优先级配置里replica-priority数值小的优先如果优先级一样就比较复制偏移量数据越新鲜越优先再不行用runid字典序兜底。选定新master后Leader向它发送slaveof no one让它变主并等待它宣布成为master。其余从节点接到指令把新master作为自己的master建立复制。旧master如果恢复上线Sentinel会把它重置为新master的从节点避免出现一老一小两个master互殴。整个切换过程里业务层会看到一瞬间的“写失败”或“无法连接”这是正常的。想要缩短这个时间窗口可以把Sentinel与master的探测频率调高一些但代价是误判概率也会上升。生产环境下3到10秒的切换时间是可接受的。2.5 哨兵搭建实操三步跑起来说了这么多原理不实操等于白看。假设你已经有一个master10.0.0.11:6379和一个slave10.0.0.12:6379再准备三台机器分别装Sentinel。哨兵的配置文件可以直接抄port 26379 daemonize yes logfile /var/log/redis/sentinel-26379.log sentinel monitor mymaster 10.0.0.11 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1如果master配置了requirepass还需要加一句sentinel auth-pass mymaster 你的密码否则哨兵查不到master状态后面会报NOAUTH错误。2是quorum最少两个哨兵判定master挂了才判odown。parallel-syncs控制故障转移后同时向几个从节点发复制命令。设1最稳挨个来设大一点恢复速度快但新主压力陡增。failover-timeout是故障转移的超时时间给足180秒避免网络抖动导致切换被中途取消。然后在三台机器上分别启动redis-sentinel sentinel.conf。你可以用sentinel get-master-addr-by-name mymaster验证哨兵是否识别到当前master再执行sentinel master mymaster看当前状态。三个Sentinel之间会自动组网不需要额外配置互相发现它们通过master元数据和发布订阅互相感知。客户端连Sentinel时要用支持Sentinel模式的客户端。比如JedisSetString sentinels new HashSet(); sentinels.add(10.0.0.11:26379); JedisSentinelPool pool new JedisSentinelPool(mymaster, sentinels);客户端会去Sentinel问“mymaster现在的主节点是谁”拿到的地址才是真正要连的那个Redis。如果发生了切换客户端会自动感知新master。千万不要在客户端配置里写死master的IP。2.6 脑裂和数据丢失哨兵模式的阿喀琉斯之踵哨兵方案能自动切换但并非完美。一个很经典的问题master和Sentinel之间网络分区但master本身没死还能对外服务。此时Sentinel判它odown选中一个slave晋升为新master。当网络恢复后旧master回来Sentinel会把它降级为新master的slave并把旧master上分区期间积累的写数据清掉。业务侧看到的就是一部分用户写到了旧master的分区数据最后全部丢了。这个问题可以通过两个参数缓解min-replicas-to-write老版本叫min-slaves-to-write和min-replicas-max-lag。含义是如果master拥有的可用从节点少于N个或者从节点的复制延迟大于M秒master就拒绝处理写请求。比如min-replicas-to-write 1表示master至少要有一个健康的从节点才接受写入否则写失败。这样在网络分区导致与所有从节点断开时旧master写入直接被拒能在一定程度上收敛数据丢失。注意它付出的代价是可用性下降从节点全挂时写入直接报错。但一致性和可用性总要取舍这两个参数就是给业务一个选择。再补一刀持久化。哨兵模式下如果一个master从来没开启过持久化不少人拿内存当纯缓存连save和appendonly都不开切换后如果新的master也没持久化一旦机器重启就是全军覆没。建议至少开启AOFappendonly yes配合appendfsync everysec性能和可靠性相对均衡有条件的用AOFRDB混合持久化兼顾恢复速度和可靠性。3. 集群机制深度拆解水平扩展与自动切换3.1 集群要解决的根本矛盾哨兵解决的是“可用性”但数据量变大之后单台Redis的内存和CPU就扛不住了。老板说加台机器你以为把数据分一分就好实际上得解决三件事数据怎么分、节点之间怎么互相知道彼此状态、某个节点挂了流量怎么接。Redis Cluster就是官方拿来同时解决这三件事的方案。它把数据切分到多个主节点上每个主节点又能带若干从节点相当于把哨兵的“自动切换”能力下放给集群里的多个分片同时使用。3.2 哈希槽16384个格子的分配逻辑第一次看到“哈希槽”hash slot这个词感觉很绕。其实一句话就能懂Redis Cluster把整个可存储的key空间虚拟成16384个格子每个key用CRC16算法算出一个数字再对16384取模得到它属于哪个槽。然后由集群管理员决定每个主节点负责哪些槽。比如3个主节点可以让节点A负责0-5460节点B负责5461-10922节点C负责10923-16383。这样设计的好处是扩容缩容不需要重新哈希所有数据只需要把一部分槽从旧节点搬到新节点即可按“槽”粒度迁移比全量重排便宜得多。客户端只需要知道key与槽的映射以及节点与槽的映射就能定位数据。但有坑要提醒这种按槽分片对多key操作不友好。比如MSET想把三个key一次性写进去但它们在CRC16计算后很可能落到不同槽跨节点的批量操作就没法用简单命令完成。Redis的解决办法是hash tag只在{}括号内的部分参与哈希。比如user:1001:login和user:1001:profile本身不是同一个槽写成user:{1001}:login和user:{1001}:profile两边哈希只会对{1001}里的1001计算必定落进同一个槽这样就能用事务、Lua脚本、MSET这些多key操作了。当然用得太多会带来数据倾斜等于把一个热用户压到同一个节点上得权衡。3.3 节点之间的互相关注Gossip协议和Cluster Bus集群里每个Redis实例除了服务端口6379还会额外开一个Cluster Bus端口通常是16379。节点之间通过这个端口互相发Gossip消息定期交换状态。它的工作方式很像工作群聊每个节点把“我看到的集群成员状态”随机挑几个伙伴广播过一段时间所有人手里的成员视图就能收敛一致。因为有了Gossip协议集群实现了“去中心化”没有哪个节点是独一无二的总指挥任何一个节点都能回答客户端的路由问题。这既是优点也带来调试困难。比如某个节点网络慢它广播的消息会携带陈旧的节点信息其他节点可能把“我”标记为疑似下线等网络恢复Gossip又慢慢把状态洗干净。所以集群节点之间对网络抖动比较敏感部署时尽量放在延迟低、稳定的内网。3.4 从节点竞选集群自己的故障转移集群的master挂掉不需要外部组件盯着主节点们自己就能感知到它们不断通过Cluster Bus探测如果某个master超过cluster-node-timeout时间没有响应就会被标记为fail。此时它名下的从节点会参与竞选。基本规则是从节点监控的master如果fail了并且从节点自己与master断开的时间足够长、复制偏移量已经追到了合适位置它就能发起投票其他主节点会投出认可票得票超过主节点总数一半就能晋升为新master。这个设计有点像“每个分片内置了一个小哨兵”。需要强调集群里即使没有从节点主节点挂掉后那部分slot的写入会失败服务整体不可用。这就是为什么生产上一定要给重要分片配从节点。另外集群默认开启cluster-require-full-coverage yes当有部分slot不可达时集群会拒绝整个集群的写入这么设计是为了避免脑裂导致各写各的宁可牺牲一小段时间的可用性。3.5 MOVED和ASK客户端怎么找到正确的节点集群内部对客户端是“询问式”路由。客户端随便连一个节点发命令如果key恰好在那个节点上直接返回不在的话节点会回一个MOVED错误告诉你“这个key的槽已经从A节点移动到了B节点以后去B吧”。客户端拿到MOVED后会更新自己缓存的槽位映射表下次直接访问B效率高但第一次必然有一次额外网络往返。迁移过程中更微妙一个槽可能在源节点和目标节点之间过渡。此时客户端访问到目标节点目标节点发现自己有部分数据没迁完会回ASK错误表示“这个key目前还在旧节点请去旧节点取一下”并提示客户端发送ASKING命令以明确这一次只是“临时访问”。MOVED是永久性的槽位变更ASK是迁移过程中的一次性临时指令。对普通业务来说现在主流的高级客户端库已经把这些错误处理封装掉了但你要知道它们在背后做什么遇到异常才能快速定位。3.6 集群搭建实操从三个主节点开始用redis-cli可以一条命令把6个实例拉成集群。假设有6台机端口均为6379redis-cli --cluster create \ 10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \ 10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \ --cluster-replicas 1这个命令会把前3个设为主节点后3个分别作为从节点。结束后你会看到类似[OK] All 16384 slots covered的输出。之后可以运行redis-cli --cluster check 10.0.0.1:6379检查状态或者连接任意节点执行CLUSTER INFO看看slots是否全部被分配。这里有个技术细节每个集群节点必须配置cluster-enabled yes并且cluster-config-file nodes.conf要指向独立文件。需要注意Redis 5以上才直接用redis-cli --cluster更老的版本用的是redis-trib.rb脚本新版本用户可以直接忽略。还有一个容易被新手忽略的坑cluster bus端口需要在防火墙或安全组放行否则节点之间互相PING不到集群一直处于“疑似下线”状态。如果业务增长需要扩容可以用redis-cli --cluster add-node 新节点:6379 现有节点:6379把新节点加入然后用redis-cli --cluster reshard从老节点匀出一些槽位。缩容时用redis-cli --cluster del-node删除节点但要确认该节点不持有任何slot否则得先把槽搬走。槽迁移期间业务会偶发延迟变大建议在低峰期操作并先做备份。3.7 集群的“槽点”这些限制别等上了生产才发现第一集群模式下不支持跨slot事务、Lua脚本和MultiKey操作多条命令如果涉及不同槽位就会报CROSSSLOT错误。第二集群只支持DB 0用SELECT 1会直接报错。第三集群节点之间的复制仍旧是异步的主节点崩了照样可能丢数据与哨兵没有本质区别只靠集群并不能解决数据一致性问题。第四集群做故障转移时从节点接管主节点后客户端路由表更新期间部分key会出现短暂的全量重定向如果客户端库缓存刷新不及时可能连续报MOVED这不一定是bug。第五大key在集群中更危险一次迁移一个大key会长时间阻塞节点造成其他slot访问超时。所以需要在设计阶段控制key粒度别往Redis里塞大对象。4. 哨兵还是集群到底怎么选4.1 一张表看懂两者的定位差异我把两者的核心差异整理成一张表方便你选型时直接对照。维度哨兵(Sentinel)集群(Cluster)数据容量受单机内存限制可通过分片横向扩展分片不支持需自己做原生按哈希槽分片故障转移有Sentinel决策有主节点投票从节点晋升多key操作无跨slot限制因为不分片跨槽操作受限可用hash tag绕过客户端复杂度较低只需Sentinel地址需支持Cluster路由并处理MOVED/ASK适用规模总数据量不大、QPS可控数据或流量突破单机瓶颈运维成本简单中等槽迁移、节点管理更复杂一致性选择分区时旧master可能继续写入有脑裂风险多数节点认可时切换失联槽可能拒绝写入4.2 几个典型选型场景如果业务Redis总内存只有几个GB到几十GB读写QPS不上十万级别团队又不想引入太多运维负担主从加哨兵是最舒服的方案。它结构清晰排查问题快甚至一个会Redis的人就能hold住。如果业务量成长到几十GB甚至几百GB单机内存和单节点QPS捉襟见肘就该上集群。多说一句集群并不仅仅是“高可用方案”它更是“容量方案”。这就解释了为什么很多团队数据量不大时宁可留在哨兵上也不集群集群带来的约束比如槽位、跨key、运维复杂度不是小团队愿意背的。还有一个真实情况不少人以为集群一定比哨兵高级就用集群替代哨兵。结果一上来就报CROSSSLOT业务又要改造。所以选型的核心是“先看容量和流量再看一致性级别”不是越复杂越好。4.3 混合架构复杂业务下的现实解法现实中不少团队是“哨兵集群”混着用的热数据、大容量数据走Redis Cluster某些对一致性要求稍高、但数据量小的场景配独立Redis加哨兵。尤其是缓存场景明明可以容忍丢失非要为了“高可用”把架构弄成集群再为了多key操作用hash tag打补丁成本实在不划算。我的习惯是先按业务模块划分数据量大且读写密集的模块独立Cluster数据量小但存着敏感状态的模块直接Sentinel加主从并加上合理的持久化。这样故障影响被局限在一个模块内排查时不用在所有分片里翻来翻去。5. 高可用架构下的常见问题与排查实录5.1 故障1Sentinel一直报NOAUTH现象Sentinel启动后日志疯狂刷# Master replied to PING with an error: -NOAUTH Authentication required。原因很简单master设置了requirepass但sentinel配置里没有对应认证信息。在每台Sentinel的配置文件里加上sentinel auth-pass mymaster 你的密码然后重启Sentinel。注意如果master或slave也配了密码从节点同步时同样需要主库密码redis.conf里的masterauth也要一起配置否则切换后新从节点一直连不上新主。5.2 故障2failover之后某个从库复制一直起不来现象哨兵完成了主从切换大部分从库都指向了新master但有一个从库日志一直报MASTER - REPLICA sync started却迟迟不结束。可能原因是该从库本身网络不通或者它配置的master地址变了但进程还没重新加载配置。可以用INFO replication查看role与master_host字段如果不是新master手动执行SLAVEOF 新masterIP 新masterPort。还有一个容易被忽略的场景旧master恢复后被降级为从库但它的数据文件和内存快照比新master老很多全量同步非常耗时期间表现为“看起来没同步”。此时不要强行换master等全量拉完就好除非业务等不了、你愿意接受丢数据。5.3 故障3集群客户端持续报MOVED性能劣化现象业务用cluster客户端连接集群平时很慢日志出现大量MOVED重定向。初看是客户端槽位映射表过期但真正原因是早期连接时节点地址用的是内网IP客户端的host配置指向了域名或对外IP返回节点信息时地址和客户端缓存不一致导致反复定向。解决办法是让集群所有实例统一使用“客户端可达的地址”不要一会儿内网一会儿域名。另外如果集群刚做完reshard业务还在老节点上重启客户端连接池让缓存刷新即可。这种问题大部分输在地址统一性上。5.4 故障4集群节点没挂却一直PFAIL现象CLUSTER INFO出现cluster_state:fail节点明明活着但日志显示其他节点认为它PFAIL。排查思路第一看cluster bus端口16379是否被防火墙、安全组挡掉第二看集群节点之间是否有双向TCP连通第三检查所有节点的集群配置是否一致。如果一台机器上同时跑多个Redis实例尤其注意cluster-config-file要各自独立千万不要共用同一个nodes.conf文件否则节点发现自己和别人的ID一样直接验证失败。最折磨人的是这种故障只有在压测或瞬时流量时出现流量一低又恢复正常多半是网络带宽打满导致Gossip消息严重延迟本质上还是资源容量问题。5.5 故障5脑裂引发的数据不一致现象哨兵模式下主从切换完成后有一个旧master还在顶着自己的身份接受写入。这不是没有而是很常见。需要先在旧master上查它是否还连着从节点如果复制链接已经断了而且它的从节点全部离线那这个节点基本就是“孤岛”。配置min-replicas-to-write 1和min-replicas-max-lag 10让旧master在孤立状态下拒写。注意这个配置对哨兵和普通主从都适用集群模式可以通过集群的fail策略和从节点投票自行处理不过在集群里节点失联也是通过gossip真正脑裂理论也存在只是概率和影响范围更小。5.6 故障6内存快满全量同步反复拖死现象加从库时全量同步一直失败主库RDB生成、传输过程中内存或带宽被打满。全量同步的RDB会fork子进程在Copy-On-Write机制下如果父进程持续写入内存会成倍增长。所以要评估maxmemory预留30%以上空闲内存。给从库做全量同步最好在低峰期。如果同步窗口太大可以用psync的分段同步能力调大repl-backlog-size尽量走部分重同步而不是全量。再一个实用技巧先看看主从数据差距多少如果差别很大就别让新的从库一上来就全量同步可以先用RDB文件离线导入再挂载为从库能省大量时间。5.7 业务侧的经验分布式锁与“incr不准”和架构有什么关联有时你在集群里用Redis做分布式锁主节点成功加锁但命令还没同步给从节点它挂了从节点晋升后锁就没了另一个客户端也能拿锁。这不是Redis的锅是“高可用与强一致天然冲突”。类似地有人反馈“INCR不准”故障转移或网络抖动时命令可能执行了但响应丢失客户端重试导致计数多算一次故障切换时主从复制丢失计数还会回退。如果计数必须准确核心思路是把计数做成“可重放”客户端带上唯一序列号业务端做幂等或用Lua保证读取加修改加写入的原子性但注意集群下所有key必须落在同一槽。别指望靠Redis的高可用机制去掩盖分布式系统的一致性边界。文章最后不打算再总结什么“核心架构三大要点”就分享一点我做高可用的真实体会。团队每次上线Redis高可用第一件事不是说“我们配好了哨兵就万事大吉”而是要趁业务流量低时做一次故障演练拔掉master网线关掉Sentinel所在机器观察切换时间、数据丢失量、客户端报错情况。等演练结果达到你能接受的范围再讲高可用才踏实。哨兵和集群原理不难难的是在真实网络环境下知道它会怎么做选择这些选择只看代码永远看不明白跑一遍故障才能让团队所有成员理解。另外再提一个容易被忽略的小配置给所有Redis实例包括Sentinel节点配上logfile和监控告警切换发生时告警必须能在1分钟内触达。否则高可用机制冷冰冰地运行着故障切换发生了你都不知道这才是真正的高可用事故。