
前阵子帮一个朋友处理线上Redis故障他们用的是最基础的主从架构主节点半夜宕机从节点虽然数据齐全但因为没有哨兵只能人肉上去改配置、把从节点提升为主节点折腾了一个多小时才恢复。这件事之后我一直想写一篇把Redis部署架构讲透的文章正好借这次机会把主从、哨兵、集群三套架构的优点和缺点做个系统对比不整虚的全是实际搭建和运维里会碰到的东西。网上聊Redis架构的文章很多但大部分要么只讲原理不讲坑要么只讲搭建不讲选型。这篇文章我会从三个层面展开第一每种架构到底解决了什么问题、又留下了什么问题第二三者的核心机制和工作原理用一个能记住的方式讲清楚第三实际部署时的配置细节和故障切换的操作流程。无论你是刚接触Redis的小白还是已经用了主从但想升级到哨兵或集群的开发者这篇文章都值得看完。1. 三种架构不是升级关系是不同目标下的三套部署方案很多初学者容易有个误区觉得主从、哨兵、集群是Redis的三个版本好像用了集群就比用了主从高级。这是理解上的偏差。这三套架构解决的问题完全不同对应的部署复杂度、运维成本、适用场景也完全不同它们之间不是简单的替代关系而是根据业务需求选择的不同工具。1.1 单机Redis什么时候开始撑不住先看最原始的形态一台Redis实例独立运行。在数据量不大、并发不高的场景下单机完全没问题配置简单、运维省心。但业务起来之后会碰到三类典型问题。第一类是数据安全的问题。Redis默认是内存数据库虽然支持RDB和AOF持久化但持久化本身不等于高可用。一旦服务器宕机、硬盘损坏或误操作执行了FLUSHALL数据照样可能丢。单机模式下没有任何冗余数据安全完全押在那一台机器上。第二类是读性能的瓶颈。Redis单实例的读性能其实很猛官方Benchmark轻松能到10万QPS以上但业务量继续往上涨或者某个热点数据被疯狂读取时单实例的CPU和网络带宽还是会打满这时候就需要把读请求分散到多台机器上。第三类是写性能和容量的瓶颈。单机的内存是有限的就算你用256GB内存的服务器数据量到了TB级也扛不住。而且Redis是单线程模型一次写命令不管大小都要排队写并发特别高的时候单实例也会成为瓶颈。这三类问题光靠调优参数解决不了必须在架构层面想办法。于是就有了分布式部署的三种典型方案。1.2 演进主线先做冗余、再做自愈、最后做扩展这三套方案对应的演进思路其实是一条非常清晰的主线。主从复制解决的是数据冗余和读能力扩展的问题。主节点负责写从节点同步数据并提供读服务。即使主节点挂了数据还在从节点那里不会彻底丢光。但从节点本身不会自动上位需要人工介入。哨兵模式解决的是故障自愈的问题。在主从的基础上引入一个独立的哨兵集群持续监控主从节点的健康状态。发现主节点宕机后哨兵自动从从节点里选出一个新的主节点业务无需人工介入就能恢复。Redis Cluster解决的是数据容量和写能力水平扩展的问题。通过数据分片把数据打散到多个主节点上每个主节点都可以处理读写请求数据量和写性能可以随着节点增加而线性扩展同时集群内部也自带高可用机制。理解了这条主线再回头看你自己的业务其实就很好选了如果你的Redis挂了可以接受人工恢复那主从够了如果希望故障自动恢复同时又不想引入集群的复杂度那就上哨兵如果数据量已经超过单机内存或者写并发高到单机撑不住那就必须上集群。2. 主从复制读写分离与数据冗余但故障恢复全靠人肉运维主从复制是Redis最基础的分布式形态其他两种架构都建立在主从复制之上。理解了主从复制的工作原理后面哨兵和集群的很多行为就都能想通了。2.1 主从复制的核心机制全量同步与增量同步主从复制的本质是让一个或多个从节点Replica实时同步主节点Master的数据。从节点通过一条复制连接向主节点请求数据主节点推过来从节点应用最终两者的数据保持一致。从节点首次连接主节点时触发的是全量同步。流程大致是这样的从节点发送PSYNC命令请求同步主节点执行BGSAVE生成RDB快照然后把RDB文件发给从节点同时在缓冲区里记录这段时间产生的写命令。从节点加载完RDB后主节点再把缓冲区里的写命令补发过来这样就追上了主节点的最新状态。全量同步之后是增量同步。主节点会维护一个复制积压缓冲区repl_backlog当从节点断线重连后如果断线期间产生的写命令还能在缓冲区里找到就用增量同步。判断依据是复制偏移量offset主节点和从节点各自维护一份偏移量重连时从节点上报自己的偏移量主节点检查偏移量之后的数据是否还在积压缓冲区里在的话就只补发这部分数据不在的话就退化成全量同步。这个机制本身设计得没毛病但实际运维中有一个值得注意的点repl_backlog_size参数默认只有1MB。如果你的写流量很大从节点断线时间稍微长一点积压缓冲区就被写命令覆盖了重连时只能做全量同步。全量同步在主节点内存大的时候非常耗时期间主节点还要执行BGSAVECPU和IO都有明显抖动。我的建议是写压力大的实例把这个值改到64MB甚至更大具体算法是每秒写命令的大小乘以能够接受的断线重连时间。2.2 主从架构的优点简单、可靠、读扩展立竿见影主从复制最大的优势就是足够简单。配置一个从节点只需要在从节点的redis.conf里加一行replicaof 192.168.1.10 6379重启之后从节点就会自动开始同步数据。没有额外的组件不需要单独部署哨兵也不用处理分片路由的复杂度。一份配置下去数据就多了一份备份这在很多中小团队里已经是最划算的容灾手段了。主从架构带来的第一个直接收益是读能力扩展。主节点只处理写请求读请求被路由到多个从节点上整体读吞吐可以成倍提升。很多真实业务都是典型的读多写少比如商品详情、配置信息、热点新闻读请求占90%以上靠主从复制就能有效减轻主节点的访问压力。第二个收益是数据冗余。既然数据有多份拷贝即使主节点所在服务器硬件损坏从节点上还有一份完整的数据可以快速接替。配合RDB和AOF持久化数据安全系数高了一个级别。另外这种架构还天然支持一主多从和链式复制。如果从节点数量很多可以考虑让其中一个从节点再作为其他从节点的主节点形成树状结构减轻主节点的推送压力。这个玩法不算复杂但在从节点数量较多时很实用。2.3 主从架构的痛点不能自动切换写容量也没扩展说完了优点接下来是主从架构的致命短板。我在开头提到的那个真实案例就是最好的例子主节点宕机之后整个系统进入主库不可写的状态。虽然有从节点可以继续提供读服务但因为没有自动故障转移机制必须由运维人员手动执行一系列操作先把一个从节点提升为主节点再修改其他从节点的replicaof配置指向新的主节点最后还要修改应用程序里的Redis连接地址并重启应用。这一整套操作下来几十分钟甚至一两个小时都算正常对于要求高可用的线上业务来说这是不可接受的。另外一个容易被忽视的问题是主从复制解决不了写瓶颈和数据容量问题。所有写请求仍然只能打到那一个主节点上主节点的内存上限仍然约束着整个Redis能存多少数据单线程的写性能上限也仍然卡在那里。读写分离只是把读的压力分担出去了写的压力一分没少。还有一个隐蔽问题是主从之间的数据一致性和延迟。当主从网络出现抖动或从节点压力过大时从节点的数据会滞后于主节点。业务如果刚从主节点写入一个数据马上从从节点去读很可能读不到这就是常见的主从延迟问题。虽然可以通过WAIT命令让写操作等待从节点确认但这对性能影响比较大生产环境一般需要根据业务容忍度做取舍。换句话说主从架构适合那些读多写少、能接受人工故障恢复的业务。如果没人半夜爬起来做手动切换那主从只是一个备份方案谈不上高可用方案。3. 哨兵模式实现自动故障转移的高可用方案如果说主从复制解决了数据有备份那哨兵解决的就是故障自动恢复。哨兵模式Sentinel在主从架构之上增加了一层独立监控系统时刻盯着Redis节点的健康状况一旦主节点故障哨兵会自动完成新主节点选举和拓扑切换。3.1 哨兵的工作机制监控、判断、选举、转移哨兵本身是一个独立运行的Redis进程默认端口26379不存储业务数据只做监控管理。一组哨兵通常部署3台或5台它们自己也会互相通信目的是避免单点故障。每个哨兵进程会周期性地做三件事每10秒向所有主从节点发送INFO命令获取最新的拓扑信息每2秒向一个内部频道发布自己的状态同时订阅这个频道来发现其他哨兵每1秒向所有已知的节点和哨兵发送PING检查对方是否存活。当某个哨兵在指定时间内down-after-milliseconds参数控制没有收到主节点的PING响应这个哨兵会把主节点标记为主观下线Subjectively Down。注意这只是单个哨兵的判断可能是因为网络抖动或者哨兵自己的问题不一定代表主节点真的挂了。为了确认这个哨兵会向其他哨兵询问主节点的状态如果收到超过quorum数量配置里的2就表示至少2个哨兵的确认就把主节点标记为客观下线Objectively Down。确认客观下线后哨兵集群内部先通过Raft算法选举出一个Leader哨兵由这个Leader负责执行故障转移。Leader会在所有从节点中挑一个作为新主节点挑选规则按优先级排序先看从节点的优先级配置replica-priority值越小越优先优先级相同看复制偏移量谁数据越新越优先最终再根据运行ID排序。选定新主节点后Leader会向它发送SLAVEOF NO ONE命令让它正式变成主节点然后向其他从节点发送SLAVEOF命令让它们去复制新的主节点如果旧主节点恢复了它也会被降级为新主节点的从节点。发生在10秒到30秒内这就是高可用的核心。3.2 哨兵模式的优点业务无感恢复可靠性大幅提升哨兵模式最核心的价值就是把故障恢复从分钟级的人工操作变成秒级的自动切换。业务应用通过哨兵的接口查询当前的主节点地址主从切换完成后应用可以感知到新地址继续读写整个过程不需要人工介入。而且哨兵显著提升了对主节点状态的判断准确性。多哨兵投票机制能有效避免因为单点网络问题导致的误判。比如一个哨兵和主节点之间的网络断了但主节点其实运行得好好的如果只有这一个哨兵它可能就会误判并触发切换但有了多个哨兵其他哨兵能正常连通主节点就不会达到quorum切换也就不会发生。哨兵模式还保留了主从架构的所有优点读扩展能力还在数据冗余还在。你可以在主从的基础上平滑增加哨兵进程结构上变化不大但整个系统的可用性直接上升了一个档次。3.3 哨兵模式的短板自动切换不等于完美写入能力仍然受限哨兵模式有两个比较硬的短板。第一个短板是写入能力和存储容量仍然受限。哨兵解决的是高可用问题并不涉及数据分片。所有写请求依然全部落在唯一的主节点上单机内存上限依然是容量天花板。如果你的数据量超过单机内存或者写请求特别大哨兵模式解决不了。第二个短板是切换期间存在短暂不可用窗口。从主节点被判定客观下线到选出新主节点再到从节点完成数据同步这个过程通常需要10到30秒。在这段时间内Redis对外只有读能力没有写能力。如果业务完全不能接受这几十秒的写中断光靠哨兵也不够。另外哨兵模式还有几个运维里容易踩的坑。一个是脑裂问题原主节点网络抖动被哨兵判为下线新的主节点已经选出来了但原主节点并没有真正宕机网络恢复后可能短暂地同时存在两个主节点接受写入。解决思路是配置min-replicas-to-write和min-replicas-max-lag让主节点在写命令时至少要有N个从节点确认在线否则拒绝写入这样在脑裂场景下能最大程度减少数据丢失。另一个坑是我在实际中碰到的也是很多人在搜索的问题哨兵模式启动后一直看不到日志变化。这个问题我放到最后面的实操部分专门讲它通常不是哨兵进程坏了而是配置里的小细节没处理干净。4. Redis Cluster用分片解决容量和写扩展代价是复杂度Redis Cluster从3.0版本开始正式发布它和前两种架构有本质区别不再依赖一个中心主节点而是通过数据分片让整个集群由多个主节点共同承担读写。每个主节点存储一部分数据同时可以配置从节点实现高可用。4.1 哈希槽与路由机制数据是怎么被拆开的Cluster的数据分片不是简单的按节点数量取模而是引入了一个固定大小的**哈希槽Hash Slot**概念。整个集群一共划分为16384个哈希槽每个key通过CRC16算法计算出一个16位哈希值再对16384取模得到一个0到16383之间的槽号。集群中的每个主节点负责一部分连续范围的槽位。举个例子三主节点的集群槽位通常这样分布节点A负责0-5460号槽节点B负责5461-10922号槽节点C负责10923-16383号槽。当你执行一个命令时客户端先算key对应的槽号再找到负责这个槽的节点把请求发过去。这带来一个关键机制MOVED重定向。如果客户端把请求发到了错误的节点比如槽位本来在B节点请求发给了A节点A节点会返回一个MOVED错误携带正确节点的IP和端口客户端收到后执行重试。优秀的客户端比如Lettuce、Jedis的Cluster模式会缓存槽位和节点的映射关系正常情况下不需要频繁重定向。Cluster的高可用机制和哨兵类似但内嵌在集群里不用额外部署组件。集群内的主节点之间通过Gossip协议互相通信持续交换节点状态。当一个主节点被多数主节点判定为故障后集群会在它的从节点里选举出一个新的主节点接管原来的槽位。4.2 Cluster的优点水平扩展、写能力提升、高可用内建Cluster最直观的优势就是容量可以水平扩展。数据散落在多个主节点上每增加一个主节点整个集群的内存容量就多一份不再受单机内存上限的约束。对于几TB甚至更大数据量的场景这是唯一现实的选择。写性能也顺势获得了扩展。因为写请求分散到多个主节点上多个主节点并行处理集群整体的写吞吐可以随着主节点数量增加而提升。注意这并不意味着某个单key的写性能提升了而是整体的并发写能力增加了。高可用方面Cluster虽然引入了一些新概念但故障转移是内建的从节点会自动接管故障主节点的槽位不需要单独部署哨兵。再加上官方运维工具redis-cli自带cluster支持槽位迁移、集群管理这类操作都有现成命令。4.3 Cluster的代价多键操作受限、运维复杂度明显上升世界上没有免费的午餐Cluster最大的代价是多键操作的限制。在单机或哨兵模式下你可以用MGET一次性获取多个key可以用LUA脚本执行跨多个key的逻辑。但在Cluster模式下只有当多个key的哈希槽一致时才能执行这类操作否则会报CROSSSLOT错误。解决办法是使用哈希标签Hash Tag。key里用花括号括起来的部分会参与哈希计算其他部分不参与。比如{user:100}:profile和{user:100}:orders因为花括号内部分都是user:100所以两个key会落在同一个槽位上就可以用MGET同时获取。但哈希标签用多了会让特定的key扎堆在少数节点上破坏数据分布的均匀性需要谨慎设计。第二个代价是客户端复杂度上升。非Cluster模式的客户端只需要配置一个连接地址就能开始读写Cluster模式的客户端必须实现槽位映射、MOVED重定向、ASK重定向、节点拓扑更新等逻辑。虽然主流客户端都支持了但引入新依赖、处理拓扑刷新、排查路由问题都增加了开发成本。第三个代价是数据迁移和扩容的运维成本。扩容时要把部分槽位从旧节点迁移到新节点执行redis-cli --cluster reshard之后数据在节点间搬移期间节点负载会升高迁移如果出错还可能影响数据完整性。同时Cluster还有一个让不少人栽过跟头的配置cluster-require-full-coverage默认值是yes意思是如果任何一个槽位暂时没有节点负责整个集群就会停止接受读写请求。对可用性要求很高的业务建议把它设置为no让集群在部分槽位故障时继续服务其他槽位的数据。5. 三套架构横向对比与选型建议技术选型从来不是哪个好而是哪个更匹配你的业务阶段。下面这张表可以帮你在做技术方案时快速对比。对比维度主从复制哨兵模式Redis Cluster数据冗余有有有数据分片无无有16384个哈希槽读能力扩展支持多从节点支持多从节点支持多主节点写能力扩展不支持不支持支持多主节点容量上限单机内存单机内存多机内存总和自动故障转移不支持支持支持故障切换时间人工处理通常几十分钟约10-30秒约10-30秒多键操作支持支持受限需相同哈希槽客户端复杂度低低较高运维复杂度低中高适用场景数据量小、读多写少、可接受人工恢复要求高可用、读多写少、数据量可控数据量大、写并发高、需要水平扩展这张表基本回答了选型的大方向。我一般根据三点来判断数据量多大、写压力多少、能否接受故障时人工介入。数据量在单机内存范围内业务读多写少就算宕机了也能等人工恢复的直接上主从。比如内部管理系统的缓存挂了影响可控完全没必要引入哨兵增加维护成本。数据量还在单机范围内但对线上可用性有要求不能接受半夜人工切换的上哨兵。这是目前互联网业务里非常主流的一种形态几个Redis进程加几个哨兵进程成本不高收益明显。数据量已经超过单机内存或者写压力让单实例CPU吃紧那就必须上Cluster。这个阶段通常伴随着较大的研发和运维投入但也代表着Redis已经进入了真正的分布式部署阶段。还有一个容易被问到的场景哨兵和Cluster能混合用吗答案是不建议。Cluster自带故障转移机制不需要再叠加哨兵而哨兵模式下不存在分片概念和Cluster是两套独立的部署形态。混用只会让架构更复杂收益几乎为零。6. 实操复盘从配置到故障切换的避坑经验最后这部分我把自己踩过的坑、以及在排查别人问题时总结出来的经验整理一遍。这些配置细节和故障排查思路通常不写在官方文档的显眼位置但恰恰是实际部署中最容易卡壳的地方。6.1 搭建主从时最容易犯的几个配置错误主从配置本身很简单基本上就是一行replicaof的事但有几个细节不处理好经常会出现从节点连不上主节点日志里一堆报错的情况。第一个坑是bind和protected-mode的组合问题。Redis默认只会绑定127.0.0.1同时开启了protected-mode。如果主从节点不在同一台机器上又没有设置密码从节点连接主节点时就会被拒绝。解决办法是把bind改成0.0.0.0或者实际网卡IP同时把protected-mode设为no或者设置密码并配置requirepass和masterauth。第二个坑是密码配置不对。主节点设置了requirepass后从节点的replicaof下面必须额外配置masterauth。很多人在主节点加了密码却忘了在从节点配置masterauth结果从节点一直尝试重连日志刷屏但数据就是不同步。第三个坑是用Docker部署时忘记处理网络模式。用docker安装redis主从是最常见的快速验证方式但如果你用默认的bridge网络主从两个容器之间需要link或者加入同一网络才能互通。我见过不少人折腾了半天最后发现是容器IP变了导致replicaof配置失效。最简单的做法是用docker network create创建一个专用网络让主从容器都加入这个网络然后通过容器名互相访问。6.2 哨兵模式启动后没反应的排查链路这个问题对应的场景就是热词里那个redis哨兵模式启动未生成know。很多人的表现是哨兵进程起来了日志也没有明显报错但主节点挂了之后哨兵就是无动于衷不触发切换甚至看不到sdown和odown的状态变化。我复盘过几次类似问题排查路径基本是固定的一条线。先确认哨兵进程到底有没有读取正确的配置文件。哨兵的启动方式不是直接启动而是要指定用哨兵模式加载配置文件redis-server /path/to/sentinel.conf --sentinel如果配置文件路径不对或者用了普通redis.conf的配置方式启动哨兵进程虽然能起来但它并没有进入监控模式。再检查配置文件里sentinel monitor这一行的配置。这一行是哨兵的核心格式是sentinel monitor mymaster 192.168.1.10 6379 2如果你的主节点IP地址写的是127.0.0.1但哨兵部署在另一台机器那它监控的就不是真正的主节点故障时自然没有反应。我在帮别人排查时至少有一半的问题是出在这里。还有个容易被忽略的点是日志级别。redis默认的日志级别是notice如果你直接把哨兵进程在前台启动日志只会输出在控制台。很多人在生产环境用nohup重定向了输出然后打开文件发现没有内容第一反应是哨兵没工作。实际上哨兵是正常运行的只是没到需要输出日志的状态。可以先用前台方式启动观察控制台日志确认看到类似monitor master mymaster 192.168.1.10 6379的输出再改成后台运行。如果前面都正常可以手动把主节点停掉观察哨兵日志中是否出现sdown和odown。如果出现了sdown但一直没有odown说明哨兵数量或者quorum配置有问题比如quorum改成了3但实际只有一个哨兵进程那就永远无法达到客观下线的条件。6.3 集群故障转移的验证思路很多业务上了Cluster之后对故障转移模块从来不验证等真出事了才发现配置有问题。这里给一套可以快速验证的流程。假设集群是3主3从先用redis-cli -c连接任意节点执行CLUSTER INFO确认集群状态是ok。然后找一个主节点把该节点所在进程kill掉模拟宕机再用CLUSTER NODES观察从节点状态。正常情况下等待大概十几秒其中一个从节点会变成master并接管原来master负责的槽位另一个节点显示为fail。还有一个手动触发故障转移的场景值得记录如果你需要重启一个主节点做维护但不想真的制造宕机可以登录该主节点对应的从节点执行redis-cli CLUSTER FAILOVER这会触发一次自动的槽位接管从节点会自动升级为主节点原主节点回来后降级为从节点整个过程对读写的影响很小。这个命令在版本升级和节点维护时非常实用比我第一次用kill方式去触发优雅得多。根据我个人的经验Redis的架构选择最重要的一点是不要为了技术而技术。如果主从能撑住业务没必要为了显得高级上集群但如果业务已经跑到单机瓶颈也不要因为怕麻烦而硬扛。架构演进是跟着业务走的把每种方案的优点和代价都摸清楚选型的时候才不会慌。