资讯动态

Redis架构演进全解析:单机、主从、哨兵与Cluster实战

发布时间:2026/10/22 15:09:02 来源:尧图企业网站定制
做后端这几年Redis基本上每个项目都在用。缓存、分布式锁、排行榜、限流器甚至消息队列都能看到它的影子。但说实话很多团队用Redis的方式还停留在“把数据塞进去然后取出来”的阶段对架构层面的东西想得不多。直到某次线上Redis单点故障导致整个系统不可用或者数据量涨到单机内存扛不住才开始回头研究它的部署形态。这篇文章把我的经验整理成一条线单机模式、主从复制、哨兵模式、Cluster集群这几种常用架构模式各自的原理、适用场景、配置要点和踩过的坑一次性说清楚。内容偏实战适合正在做Redis容量规划、高可用方案或者准备从单机演进到集群的开发者参考。1. 单机模式一切架构的起点1.1 什么场景下单机Redis完全够用单机模式就是只有一个Redis实例不跟任何其他节点做数据交互所有读写都打在这一个进程上。它的优势非常直接部署成本最低、延迟最低、故障域最小。很多小型项目、内部工具、定时任务系统数据量就几个GBQPS小几千单机Redis跑一年都不会有问题。我见过不少团队在项目初期用单机Redis跑得很稳商品缓存、用户session、配置中心全塞在一个实例里性能完全够。这里有个容易被忽略的前提单机Redis虽然是单线程模型但它的性能一点都不差通常能扛住几万到十几万的QPS前提是key和value都不大命令都是简单读写。如果你的业务在这个量级以下真没必要一上来就上集群复杂架构带来的运维成本反而会成为负担。但“单机”不等于“裸奔”。即便只有一个实例也应该把内存上限、持久化策略、慢查询日志、监控指标这些基础工作做好。比如maxmemory一定要设置不设的话Redis会持续使用系统内存直到触发OS层的内存回收进程被卡死甚至被杀掉都是有可能的。再比如持久化选RDB还是AOF要结合业务能容忍丢多少数据去权衡而不是用默认配置一挂到底。1.2 单机模式最致命的问题单机最先暴露的是可用性风险。进程崩溃、服务器宕机、网络波动任何一种情况都会导致Redis在一段时间内完全不可用。对缓存场景来说Redis挂了之后所有请求会直接穿透到数据库如果流量大数据库很容易被打到连接数耗尽、慢查询堆积最终整个业务雪崩。对分布式锁场景来说锁节点挂了所有依赖锁保护的临界区逻辑都失去了保障后果更直接。第二个问题是容量和性能的扩展性。单机内存受限于物理机器数据量超过几十GB之后RDB在bgsave时的fork开销会变得非常大。fork要复制父进程的页表数据量越大fork越慢这段期间整个Redis实例可能出现秒级甚至更长的卡顿。而且RDB文件写入和传输也依赖磁盘和网络实例越大这些操作越沉重。还有一点是Redis的单线程命令执行模型。虽然Redis 6.0以后引入了多线程IO来提升网络读写吞吐但核心命令的执行依然是单线程。这意味着一个实例的性能上限基本是固定的不可能通过加CPU核数来提升。当单实例性能打满或者容量超过物理机限制就需要从架构层面想办法了。1.3 即使是单机这些配置也必须要做第一个必做项是内存上限和淘汰策略。在redis.conf里设置maxmemory 4gb maxmemory-policy allkeys-lrumaxmemory一旦不设置64位系统下默认不限制Redis会把内存吃到系统资源耗尽。maxmemory-policy决定了内存满时怎么腾空间。allkeys-lru是绝大多数缓存场景的最优解所有key都参与LRU淘汰只保留最近访问活跃的数据。如果有些key必须永不过期、不能被淘汰那就用volatile-lru只淘汰设置了过期时间的key。noeviction是另一个极端内存满了直接拒绝写入适合把Redis当数据库用的场景宁可报错也不丢数据。第二个必做项是持久化。对缓存场景来说很多人觉得Redis挂了就挂了数据丢了重新回源就行。但真到线上出了问题你会发现几分钟的缓存重建也能轻松打垮数据库更别说分布式任务调度、排行榜这类数据丢失就会产生业务事故的场景。所以持久化不能完全不开至少开RDB配合AOF更稳。save 900 1 save 300 10 appendonly yes appendfsync everysecAOF的fsync策略选everysec是性能和安全的平衡点每秒刷一次盘最坏情况丢一秒数据。除非对数据绝对安全有硬要求否则不建议用always性能代价太大。RDB和AOF同时开着Redis重启时会优先加载AOF因为AOF的日志完整性更高。第三个容易被忽略的是命令安全和慢查询监控。生产环境建议用requirepass设置访问密码同时把KEYS、FLUSHALL、FLUSHDB这类高风险命令改名或者禁用requirepass your_strong_password rename-command FLUSHALL rename-command FLUSHDB rename-command KEYS 慢查询日志也要提前配好不然哪天线上出现Redis响应变慢你会连排查的头绪都没有slowlog-log-slower-than 10000 slowlog-max-len 128slowlog-log-slower-than单位是微秒10000相当于10毫秒。把阈值设得比平时平均延迟高一截就行太低了会刷屏太高了又起不到预警作用。2. 主从复制先把读流量摊开2.1 主从复制解决什么问题不解决什么问题主从复制是一个master配多个replica写请求走master读请求可以打到replica上数据通过异步复制从master同步到replica。它最直接的价值就是把读流量分摊到多台机器上解决单机读性能瓶颈。我做过一个典型的读多写少项目读QPS有三四万写QPS只有几百单机Redis的CPU已经被读命令打满了。架构改成1主2从之后读请求分散到两个从节点主节点压力瞬间降下来整个集群扛到了十万级QPS这就是主从复制最舒服的应用场景。但要特别强调一点主从模式并不是高可用方案。master挂了虽然数据在replica上都有业务却没法自动切换必须人工介入选一个replica手动提升为master再去改客户端的连接地址。这个过程业务必然受损短则几分钟长则半小时。所以主从复制的定位是“读写分离架构”不是“高可用架构”。想实现自动故障转移需要在此基础上加哨兵。2.2 全量同步、增量同步与断点续传理解主从复制的原理要先把同步过程拆开。第一次建立复制关系时replica会发送PSYNC命令给master要求进行一次全量同步。master收到请求后执行bgsave生成当前数据的RDB快照同时把快照期间产生的所有写命令缓存到复制缓冲区里。RDB生成完后传输给replicareplica加载完RDB再把master补发的缓冲写命令执行一遍最终两边数据一致。之所以用RDB快照做全量同步的初始数据而不是把历史写命令一条条发给replica是因为RDB对网络和CPU的开销都更小。如果数据库里有50GB数据写命令可能已经积累了上千GB的量级传命令流的方式效率太低了。全量同步完成之后master会持续把新的写命令以命令流的形式发给replica这就是增量同步阶段。增量同步依赖两个东西复制偏移量和repl_backlog环形缓冲区。master和每个replica都会各自维护一个复制偏移量replica每收到并执行一条命令就推进自己的偏移量。master在发送命令的同时会把这些命令写进repl_backlog缓冲如果replica短暂断开后重连master能通过偏移量判断replica落后了多少数据。只要落后部分还留在repl_backlog里replica就可以走增量同步快速追平而不需要重新全量同步。这里有个生产环境非常关键的参数repl-backlog-size 64mb默认的repl_backlog只有1MB对写入量稍大的业务来说replica断线个几秒钟后面产生的新命令就把缓冲环覆盖了重新连线时offset已经不在缓冲区间内被迫触发全量同步。全量同步对master和网络都是巨大压力。我一般按“每秒写入量乘以期望容忍断线秒数”来估算比如写QPS 2000每命令平均500字节每秒写入约1MB断线容忍60秒backlog至少要64MB。2.3 主从配置实操配置主从关系非常轻量在replica节点的redis.conf里加一行replicaof 10.0.0.10 6379也可以用命令行临时指定redis-cli -p 6379 REPLICAOF 10.0.0.10 6379注意新版本里REPLICAOF已经取代了老的SLAVEOF虽然SLAVEOF还能用但新项目就别用了。配好后在replica节点执行INFO replication能看到master_link_status是up就说明复制链路已经建立。如果master设置了requirepassreplica还需要配置masterauth否则复制连接会被拒掉masterauth your_strong_password从节点默认是只读的读流量打到replica没问题千万别在replica上直接写数据一旦写了非预期数据主从数据就出现分叉后续同步可能直接报错。保持默认的replica-read-only yes除了基础配置我还建议在master上开写保护min-replicas-to-write 1 min-replicas-max-lag 10这两行配置的意思是如果master检测到存活且延迟小于10秒的replica数量不足1个就拒绝处理写命令。它能避免master和所有replica之间网络断开的极端情况下master继续写入导致的数据丢失问题。后面讲哨兵脑裂的时候会再提到这对参数其实是同一个思路。2.4 主从模式最容易踩的坑第一个是主从延迟导致“读己之写”失败。用户在页面上更新了一个数据立刻去读结果读到的是从节点上的旧数据体感就是“改了没生效”。原因是主从复制是异步的replica收到命令到执行完命令存在时间差写入量越大或网络越差延迟越明显。如果业务对“立即可见”有要求有几种处理办法要么让这部分请求强制读master要么更新后短时间内走master读要么业务上容忍这个延迟。不要试图用WAIT命令去强等replica同步完成它会阻塞写命令的执行性能代价太高。第二个是全量复制风暴。如果一个从节点断线后大量数据落后于repl_backlog的可回溯范围它会重新触发全量同步如果有多个从节点同时断线master就要同时处理多次bgsave和多次RDB文件传输磁盘IO、CPU、带宽都会被打满甚至会拖垮正常读写服务。我踩过一次所有从节点同时重连的坑当时master的CPU直接飙到满载线上读延迟从1毫秒涨到几百毫秒。后来加强了网络稳定性监控同时把repl-backlog-size调大减少全量同步的触发概率。第三个坑是主从节点配置不一致导致的隐患。比如master的maxmemory是8GBreplica却用了默认的0也就是不限制当replica因为复制积压或其他原因内存飙高它可能触发自己的OOM杀进程而master还认为replica正常存活。所以生产环境要求主从节点的配置保持基本一致包括内存上限、持久化策略、淘汰策略。3. 哨兵模式迈向真正的高可用3.1 Sentinel架构是怎么工作的Sentinel是在主从复制之上增加的一套自动故障转移机制。它的核心职责可以归纳为三个词监控、通知、自动故障转移。Sentinel节点会定期检查所有Redis节点的心跳状态一旦发现master不可用就自动从replica中选出一个新的master并把结果通知给客户端和其余replica整个过程不需要人工介入。从数据存储的视角看Sentinel只是运行在特殊模式下的Redis实例但它不存储业务数据而是维护一组监控目标的状态数据。生产环境通常部署3个或更多Sentinel实例构成一个小集群。注意Sentinel不是Redis的replica它的数量和Redis数据节点的数量也没有绑定关系。我曾经见过有人只在Redis节点本机各挂了一个Sentinel进程结果机器宕机时Sentinel一起没了自动化故障转移形同虚设这个部署方式一定要避免。3.2 生产环境部署Sentinel的要点Sentinel至少需要3个实例而且要部署在独立的机器上最好和Redis数据节点完全不同机。原因很简单Sentinel的多数派决策机制需要一个奇数节点数如果只有2个Sentinel它们之间出现网络分区时任何一边都凑不齐多数就无法可靠地完成故障转移。3个Sentinel能容忍1个Sentinel节点挂掉5个能容忍2个按这个逻辑往上推。Sentinel本身是一个独立的进程启动方式是在配置了sentinel相关项之后用sentinel模式跑redis-server /path/to/sentinel.conf --sentinel一个基础的sentinel.conf长这样port 26379 dir /var/lib/redis sentinel monitor mymaster 10.0.0.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 1 sentinel auth-pass mymaster your_strong_password这里的mymaster是给监控目标起的逻辑名字客户端接入时也是通过这个名字来获取实际读写地址。down-after-milliseconds表示Sentinel在判断master不可用之前持续联系不上的时长默认值是30秒对大多数业务来说太长我一般调成5秒。failover-timeout是故障转移的超时时间parallel-syncs指新master被选出后同时允许多少个replica向新master发起全量同步。这个值不要设大设置成1就好让replica一个一个地同步避免瞬间压垮新master。3.3 主观下线、客观下线与选举机制Sentinel判断master故障是分两步的这两个概念面试常问生产上理解了也有用。第一步是主观下线某个Sentinel发现自己连续down-after-milliseconds时间联系不上master就单方面把master标记为主观下线。但单个Sentinel说了不算万一只是Sentinel自己网络抖动了呢。第二步是客观下线主观下线的Sentinel会通过sentinel is-master-down-by-addr命令询问其他Sentinel问它们能否联系上同一台master。当收到足够多的确认回复达到配置里的quorum数量时比如上面的2Sentinel才把master标记为客观下线然后真正触发故障转移。这一机制是整个高可用设计的核心只有多数派Sentinel对故障状态达成共识才允许执行切换从而避免因为单个节点误判而搞出两个master。触发故障转移后Sentinel需要从所有replica中选出一个新master。选举规则是这样的优先选择replica优先级replica-priority参数最高的数值越小越优先如果优先级一样就选复制偏移量最大的也就是数据最接近旧master的那个要是偏移量也一样就选run_id字典序最小的那个。所以完全可以在replica上预设优先级比如给硬件配置较好或者处于同机房的replica设置更小的priority让它在故障转移时优先上位。3.4 脑裂问题与写保护配置Sentinel模式里最容易出事故的是脑裂场景。假设master和大部分Sentinel之间网络分区少数Sentinel仍然能连上master但它们凑不齐quorum因此无法判定master客观下线。此时分区另一侧多数Sentinel因为联系不上master判定客观下线并发起故障转移从某个replica那里选出了新master。这时候两个master同时存在客户端写请求被Sentinel导向新master但旧master的网络其实没坏它一直还在接收写入两边数据就开始分叉。网络恢复后旧master会以replica身份重新加入集群向新master发起全量同步把新master的数据覆盖过来。覆盖的结果是旧master在故障转移期间写入的所有数据全部丢失。这个丢失发生在业务层几乎无法感知但数据就是没了。缓解脑裂数据丢失的办法就是前面提到的min-replicas-to-write和min-replicas-max-lag。在master上配置至少保留1个正常同步的replica否则拒绝写入。脑裂场景下旧master联系不上任何replica写保护条件不满足写请求会被直接拒绝大大缩小数据丢失的窗口。当然这也意味着在真故障期间master短暂地不可写这是为了数据安全必须做出的取舍。3.5 客户端接入哨兵模式的注意事项使用哨兵模式时客户端不应该把master的IP和端口写死在配置里因为故障转移之后master的地址会变成原来某个replica的地址。正确的做法是配置Sentinel节点的地址列表和masterName让客户端动态从Sentinel获取当前master地址。主流Redis客户端库基本都支持Sentinel模式配置起来不复杂。实际接入中有几个容易翻车的细节。一是Sentinel本身如果开了requirepass客户端连接Sentinel也要配密码。二是master开启密码保护时replica需要masterauth配置同时Sentinel配置文件里的sentinel auth-pass也必须填对否则Sentinel联系不上master。三是故障转移完成后旧master恢复后会被降级成replica并重新同步你们家的监控和告警系统最好能感知到这种角色变化不然某个时刻会出现“明明有个Redis进程活着但业务写入全失败”的怪现象排查起来一头雾水。4. Cluster集群容量与性能的水平扩展4.1 为什么数据量大了必须用集群主从复制解决了读压力问题哨兵解决了高可用问题但它们都没解决存储容量和写性能的水平扩展问题。主节点依然是一个实例内存受物理机限制写命令执行依然单线程。当数据量超过200GB或者写QPS超过了单实例的处理上限就必须考虑分片了。有人会说那我把单机的内存加大不就行了服务器内存是能加到512GB甚至更大但Redis实例越大bgsave时的fork开销越大进程内存和页表复制带来的停顿时间越长。一个300GB的实例做一次bgsave期间可能对线上造成几十秒的明显毛刺这是不能接受的。Cluster模式的核心思想就是通过把数据分散到多个节点让每个节点的数据量保持在合理区间比如单节点控制在20GB以内这样每一个分片的持久化、同步、恢复速度都可控。4.2 槽位、Hash与MOVED/ASK重定向Redis Cluster的数据分布方式和老式的客户端分片不一样它用固定槽位来组织数据。整个集群有16384个哈希槽每个master节点负责其中一部分。写入或读取一个key时客户端计算CRC16(key) % 16384得到槽号再根据槽号定位到负责该槽的节点直接跟那个节点通信。客户端第一次可能不知道某个key在哪个节点上但没关系。Cluster中的任何一个节点接收到不属于自己负责槽位的命令时会返回MOVED错误并附带正确节点的地址。智能客户端收到MOVED后会更新本地路由缓存然后重新向正确的节点发起请求。这种设计的好处是客户端不需要完整维护一份槽位和节点的映射表只要处理MOVED就能逐步收敛到正确的路由状态。还有一个和MOVED长得像但含义不同的错误ASK。当集群在做槽位迁移时数据源节点可能已经把一部分key迁走了但又还没迁完此时源节点会对请求返回ASK让客户端先去目标节点发送ASKING命令再执行真正的操作。迁移完成后客户端的路由表就会重新指向目标节点。理解MOVED和ASK的区别排查集群相关问题时非常重要。顺便解释一个常被问到的点为什么槽位总数设计成16384而不是65535或者别的数。CRC16输出的值域是65536取16384是综合考虑了网络心跳包的开销和路由精度。集群节点之间通过Gossip协议交换状态槽位信息用bitmap表示16384个槽的bitmap是2KB如果改成65536个槽bitmap就是8KB心跳包大了四倍节点多的集群网络开销会明显上升。16384是社区反复权衡后的结论并不是拍脑袋定的。4.3 集群搭建实操生产环境最基础、最保险的Cluster部署形态是3主3从每个master配一个replica任何一个节点挂了对应分片还有备份可以顶上。6个节点可以是6台独立机器或者容器至少不能把所有节点塞进同一台物理机否则宿主机宕机等于整个集群全军覆没。每个节点的redis.conf需要开启集群模式port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 5000 appendonly yes daemonize yes准备6个不同端口或独立机器后用redis-cli一键建集群redis-cli --cluster create \ 10.0.0.10:7000 10.0.0.11:7000 10.0.0.12:7000 \ 10.0.0.13:7000 10.0.0.14:7000 10.0.0.15:7000 \ --cluster-replicas 1--cluster-replicas 1的意思是每个master分配一个replica命令会引导确认分配方案。执行完成后可以用redis-cli --cluster check 10.0.0.10:7000查看集群状态。在任意节点执行CLUSTER INFO和CLUSTER NODES也能看到slot分布、节点角色和连接状态。初次搭建时重点关注所有节点是否都处于cluster_state:ok以及16384个槽是否全部被分配且没有槽位冲突。4.4 集群模式下的使用限制与避坑集群带来的最典型限制是对多key命令的各种约束。因为一个命令涉及的所有key必须落在同一个slotmget、mset、del多个key时如果key分布在不同槽位命令会直接报错。解决思路是hash tagRedis在计算CRC16时只看key里第一个花括号中的内容。比如user.{100}.name和user.{100}.email这两个key它们参与哈希的都只是100因此会落在同一个slot可以被mget安全操作。hash tag用起来很简单但要注意不能让所有key都用一个tag否则数据全部挤到一个slot里集群就失去了分片的意义。另一个常见问题是数据倾斜。集群分片是按key的哈希值来均衡的但数据热点是动态的。某个key对应的value特别大比如一个10MB的序列化对象哪怕它只占一个slot也会让所在节点内存明显偏高这就是容量倾斜某个热点key被频繁访问哪怕数据不大也会让所在节点CPU和带宽打满这是访问倾斜。集群模式本身就难以解决这类问题只能从业务侧拆key。比如给同一个业务对象生成多个带随机后缀的key读写时先定位到其中一个。还有运维操作上的坑迁移槽位时需要用reshard操作期间被迁移的key可能会短暂不可用业务上要做容忍。不要在生产高峰期做大范围的reshard尽量安排在低流量时段。扩容节点也不是简单加一台机器就完事需要把一部分槽从老节点迁移到新节点整个流程如果操作不当可能出现某个slot没有任何节点负责这时候客户端请求命中该slot会直接收到CLUSTERDOWN错误。4.5 集群与哨兵怎么选哨兵模式和Cluster模式经常被放一起比较但其实它们解决的是不同维度的问题。哨兵解决高可用主节点还是那一个容量上限没变Cluster解决水平扩展通过分片把容量和性能横向摊开自身也具备了master节点故障时的自动转移能力所以Cluster本身可以被看作是一种“自带高可用能力的分片架构”。选型时如果数据量不大比如明确未来两三年内不会超过单机内存上限用哨兵模式就够了复杂度最低多key操作完全不受限。如果数据量已经超过几百GB或者写QPS明显超过单实例上限就果断上Cluster。最怕的是在两套方案之间反复摇摆架构来回改最后业务没怎么增长精力全耗在迁移上了。5. 架构选型对比与演进路线5.1 五种模式核心能力对照用一个表格把几种架构的差异说清楚维度单机主从复制哨兵模式Cluster集群数据容量上限单机内存主节点内存主节点内存多节点分片近乎无限扩展读性能扩展无可增加从节点扩展读可增加从节点扩展读随分片数扩展写性能扩展无不可扩展不可扩展随分片数扩展高可用无无自动切换自动故障转移分片内自动故障转移多key操作完全支持完全支持完全支持跨slot受限需hash tag运维复杂度低低中高需要迁移、reshard等操作适用场景小规模工具、开发环境读多写少的中小规模业务读多写少且要求自动高可用的业务数据量大、写入量大的大规模业务5.2 从单机到集群的渐进式演进路线架构选型有个很重要的原则不要一步到位永远只解决当前最痛的问题。我见过不少团队在业务只有几千QPS的时候就直接上了Cluster结果发现大量跨slot查询需要改业务代码运维复杂度提升了几倍收益却几乎为零。比较合理的演进路径是这样的项目初期数据量小、QPS不高先用单机模式把业务跑起来把监控和告警做好。读流量涨上来之后改成主从复制把读压力分散开。当业务对可用性提出更高要求不允许Redis长期不可用再加Sentinel实现自动故障转移。最后只有数据量或写压力涨到单实例扛不住时才迁移到Cluster而且迁移前一定要对业务代码里的多key操作做一次全面梳理提前用hash tag规划好key的分布。5.3 我在规模评估和容量规划上的一些建议容量规划是架构选型的第一步也是最容易被拍脑袋决定的环节。我个人的习惯是先统计业务当前的数据量、key总量和平均key大小然后按未来一年的增速打个至少3到5倍的余量。比如现有数据20GB一年后预计到60GB单机128GB内存的节点还能兜得住但又要考虑bgsave的fork开销那单实例控制在60GB以内比较稳妥超过这个数就该考虑Cluster了。性能规划看两个指标写QPS和读QPS。写QPS决定了主节点的压力读QPS决定了从节点或分片的总吞吐。把峰值读QPS除以单实例能力大致能算出需要多少从节点或者多少个分片。单实例能力不建议直接按理论峰值算最好用压测数据比如当前业务模型下单实例最多扛6万QPS峰值需求30万QPS那至少需要5个分片或等价的从节点数量。最后提醒一句无论选哪种架构Redis本身的运行健康度都要持续盯住。INFO里看connected_clients、used_memory、rejected_connections、rdb_last_bgsave_statuslatency里看P99和P999再加上慢查询和大key扫描。架构再优雅底层的实例状态不稳定也是白搭。我在实际项目中最大的体会是Redis的架构演进往往滞后于业务增长。大家总觉得自己还能再扛一扛结果总是在大促前或者线上事故后一边熬夜一边补架构。与其这样不如在业务相对平稳的时候把容量预测和架构升级排上计划。先想清楚每种模式到底解决了什么问题再根据自己业务的数据量、QPS和可用性目标去匹配大部分Redis架构问题都不难决策。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑