
系统架构设计师这个角色最终要回答的问题其实很朴素在有限的资源和复杂的故障环境下系统怎么搭配才能保证核心业务不塌。Redis的分布式架构设计就是这类问题最典型的缩影。这篇内容我打算把CAP和BASE理论落到Redis的真实实现上再顺着主从、哨兵、Cluster三条架构路线把高可用和集群设计完整拆一遍。如果你做后端开发、搞运维或者正在为系统架构设计师考试打基础这篇文章应该能帮你把Redis分布式相关的零散知识点串成一条完整的线。1. CAP与BASE理论也可以这样理解很多讲Redis的文章一上来就抛术语CAP是三个字母BASE是四个字母背下来容易真到架构评审时一问“你设计的方案在CAP里选了哪一头”就答不上来了。这里我不打算背书直接把三个要素放到真实分布式的场景里看。1.1 CAP三要素落地到分布式系统CConsistency强一致性讲的是同一份数据在多个节点上的状态一致并且任何读请求都能拿到最新写入的结果。注意是“任何”不是“最终”如果主节点写入了从节点还没同步客户端读从节点拿到的是旧值那就不满足强一致。AAvailability可用性讲的是每个请求都能收到响应。哪怕数据已经乱成一锅粥只要系统还能回包就还算“基本可用”。这个定义的重点在于系统不一定要返回正确数据但绝不能挂在那里不吭声。PPartition Tolerance分区容错性讲的是网络发生分区——就是节点之间的网络断了、消息丢了、延迟高到不可接受——系统依然能运转。这一点在分布式环境里是“必选”因为网络故障不是偶然是必然。你无法保证机房之间的专线永远不抖动无法保证交换机不重启所以分区一定会发生你不能选“不分区的方案”只能面对它。当分区真的发生时网络两侧的节点无法通信这时候C和A就冲突了。举个例子主节点和从节点断连客户端向主节点写入一条数据主节点无法把这条数据同步给从节点。这时候你怎么办如果为了保证一致性主节点必须拒绝写入因为写进去也没法同步另一侧节点会读到不一致的数据这就是牺牲可用性选CP如果为了保证可用性主节点照常写入等网络恢复后再补同步这就是暂时牺牲一致性选AP。两者只能二选一谁都没法既要又要。这个逻辑可以用一个粗浅的比方两家分店要靠总部系统实时对账但分店之间的网络断了。如果坚持实时对账分店就得暂停营业如果坚持营业那双方的账必然有一段时间对不上只能日后再拉平。这就是CAP的本质不是什么高深数学就是一个物理约束。1.2 BASE理论是对CAP的务实妥协BASE是Basically Available基本可用、Soft state软状态、Eventually consistent最终一致性的缩写。它的核心思想很简单在大型分布式系统里别追求每一毫秒都强一致只要整体最终能对齐就换来了整体高可用。这个思路对互联网业务尤其友好因为用户能接受“稍后刷新就看到新数据”但不能接受“页面打不开”。Redis主从架构天然贴近BASE主库写入后通过异步复制同步到从库从库的数据在同步完成之前处于“旧版本”状态这就是软状态同步完成后数据收敛一致这就是最终一致性。如果你非要在Redis上强一致也不是不行但代价往往是延迟上升、吞吐下降极端情况下直接不可用这在大多数高并发业务里是划不来的。1.3 Redis在CAP里的选型逻辑默认情况下Redis主从模式选的是AP优先核心原因是主库写入后立刻返回异步复制给从库。如果主库宕机尚未同步到从库的数据就会丢失可用性保住了一致性丢了一截。但这不代表Redis永远只能AP它提供了WAIT命令让主库在返回写结果前等待指定数量的从库确认同步完成配合参数可以近似达到CP效果。代价就是每次写入都变慢如果从库挂了写入会一直阻塞直到超时可用性下降。哨兵模式和Cluster模式也一样本质上都是为了在“不让服务中断”的前提下尽可能优化数据安全。它们用了一系列机制——比如故障转移、副本选举、多数派确认——把一致性损失控制在可接受范围内。架构模式一致性特点可用性特点适用场景单机强一致只有一个节点单点故障即不可用开发测试、容忍停机主从复制最终一致异步复制有窗口主故障需人工切换只解决读扩展读写分离、数据备份哨兵最终一致故障转移自动完成自动故障转移核心高可用方案中小规模生产环境Cluster最终一致分片间天然隔离数据分片扩展性与可用性兼顾大规模数据、高并发写入这张表建议收藏架构评审时很常用。2. 高可用架构演进路线Redis为什么一步一步走到ClusterRedis的架构不是一步到位的从单机到Cluster每一步都对应着一类现实痛点。理解这条演进路线你对架构设计的认知会比单纯背概念深得多。2.1 单机时代的三个瓶颈最早的Redis部署就是一台实例能扛的并发也还不错纯内存操作单机读写轻松数万QPS。但架构师看问题不能只看峰值要看故障域和扩展域。单机模式下Redis进程一挂所有依赖缓存、依赖分布式锁的业务全部瘫痪这是第一个问题——单点故障。第二个问题单机内存是有上限的比如一台机器64GB内存减去系统开销Redis能用的也就那么多数据量一旦超过物理上限你只能换更大的机器这是容量瓶颈。第三个问题单机的CPU和网络带宽也有限当读流量暴增单实例很容易成为瓶颈。这三个瓶颈分别引出了不同的解法。单点故障靠多副本解决容量上限靠分片解决读压力靠读写分离缓解。而主从复制、哨兵、Cluster就是这些解法的工程化落地。2.2 主从解决什么哨兵解决什么主从复制解决的是“备份”和“读扩展”。主库负责写从库同步数据后分担读流量至少主库挂了数据还在从库上不至于连数据都没了。但主从复制不解决“自动切换”主库宕机后需要人工登录服务器执行命令把某个从库提升为主库这个过程中业务是不可用的。哨兵解决的就是这个“故障转移自动化”的问题。哨兵节点专门盯着主从的健康状态一旦主库异常哨兵集群通过投票选出新主库并把所有从库都切换到新主库上客户端连的地址由哨兵动态返回。有了哨兵运维人员不需要半夜起来手工切换主库业务可用性从“分钟级人工恢复”提升到“秒级自动恢复”。2.3 Cluster解决的是数据规模和写入上限主从加哨兵扛住了故障但扛不住“数据量超过单机内存”的问题。你数据涨到几百GB单机根本塞不下写入QPS高到单实例CPU打满加从库也没用因为写流量只能打到主库。这时候就需要把数据打散到多台机器上每台机器只负责一部分数据。Redis Cluster就是干这个的它把整个数据空间划分为16384个槽位每个主节点负责一部分槽位客户端根据key计算到底访问哪个节点。所以集群模式不是替代哨兵而是把主从复制和故障转移做了分片版本。每个分片内部依然是主从结构一个分片的主节点挂了该分片的从节点自动顶上其他分片不受影响。架构选型时一定要从实际需求出发几百MB的数据用单机或主从完全够强上Cluster只会增加运维复杂度但如果你预估数据会涨到单机内存无法承载一开始设计就要考虑集群化的扩展路径不然后面做数据迁移的代价会很大。3. 主从复制一切高可用的起点不管你是用哨兵还是Cluster底层都离不开主从复制。这个环节可以多花点时间它是高可用架构的地基。3.1 主从复制的核心机制主从复制的配置方式不复杂在从节点的配置文件里加上一行replicaof 192.168.1.10 6379或者运行时执行命令redis-cli -p 6379 REPLICAOF 192.168.1.10 6379执行完这条命令后从节点会异步同步主节点的全量数据之后持续同步增量写入。这里要特别注意Redis主从复制是异步的主库执行完写命令后直接返回客户端不等待从库确认。这个设计带来了低延迟但也带来了数据丢失的风险窗口理解这一点后面很多问题都能解释通。从节点默认是只读的保证不会出现从节点被写入导致数据分叉。配置项replica-read-only默认为yes。另外从节点还有一个优先级参数replica-priority值越小优先级越高在哨兵选主或Cluster故障转移时会用到默认值是100。3.2 全量复制与增量复制到底怎么触发首次建立主从关系时从节点没有任何历史数据只能做一次全量同步。流程大致是从节点发送PSYNC命令主节点收到后执行BGSAVE生成RDB快照同时把新产生的写命令写入复制积压缓冲区然后主节点把RDB文件发给从节点从节点加载完成后再把缓冲区里的增量命令补上。经历全量同步后主从进入增量阶段主节点把每一条写命令实时发给从节点执行。这里有个高频问题什么情况下会触发全量复制常见有三种。第一种是从节点第一次连接主节点第二种是从节点与主节点的复制偏移量差距太大主节点的复制积压缓冲区里已经没有从节点需要的增量数据了只能从头全量同步第三种是从节点重启且丢失了复制上下文。全量复制代价非常大RDB传输、磁盘写入、CPU开销都会占用主节点资源所以要尽量避免频繁全量复制。复制积压缓冲区的大小很关键默认配置repl-backlog-size是1MB如果从节点断连超过几十秒主库产生的增量数据超过1MB从节点重新连上后就只能全量同步。生产环境建议根据写入速率和预期断连时长来调整比如写入速率是5MB每秒希望支持从节点断连60秒后还能增量续传那缓冲区至少要300MB以上。repl-backlog-size 512mb3.3 复制延迟与读取旧数据的坑主从复制虽然是实时的但仍有延迟。从节点在同步命令前需要经过网络传输、主节点命令分发、从节点命令执行每个环节都可能造成积压。如果从库加载了慢查询或者主库写入量陡增从库数据落后主库的时间可能从几毫秒拉到数秒甚至在极端写压下到分钟级。最常见的坑是读写分离客户端把读请求分流到从库如果主库刚写入的数据还没同步到从库读请求就会查到旧数据。业务上如果对实时性要求极高这类数据就不应该路由到从库或者给这类操作增加一个“强制读主库”的开关。监控主从延迟可以用命令redis-cli -p 6380 info replication重点关注输出里的master_repl_offset和slave_repl_offset两者的差值就是主从的复制滞后量。差值越来越大说明同步速度跟不上写入速度。另一个更深层的坑是数据丢失。主库刚写入一条数据尚未同步给从库主库进程就崩溃了。这时由于从库没有这条数据故障切换后这条写入就找不回来了。还有更隐蔽的脑裂场景会在后面哨兵部分详细展开。4. 哨兵机制把故障转移自动化主从复制能解决备份问题但解决不了“主库挂了怎么办”。哨兵就是为这个问题生的。4.1 哨兵到底在架构里干了什么哨兵是一组独立的Redis进程它不存业务数据只做三件事监控、通知、自动故障转移。监控指的是哨兵周期性向所有主从节点发送PING命令检查它们是否存活通知指的是当被监控节点的状态发生变化时哨兵会通过发布订阅机制通知系统管理员或其他客户端自动故障转移是核心能力当主库被认为失效后哨兵会自动选出一个从库提升为新主库并把其他从库指向新主库。哨兵本身不能是单点生产环境至少要部署三个哨兵节点并且奇数个。为什么奇数这里涉及一个重要的机制判断一个主库是否真正下线。单个哨兵发现主库心跳超时会先标记为主观下线对应状态的描述是sdown表示“这个哨兵自己觉得它挂了”。但一个哨兵说了不算怕误判。哨兵之间会互相通信如果多个哨兵都认为主库下线且数量达到quorum阈值就会把主库标记为客观下线对应odown这时候才会触发故障转移流程。核心配置示例sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 30000这里的2就是quorum代表至少两个哨兵同意主库下线才会执行故障转移。down-after-milliseconds是判定节点失效的超时时间默认30秒生产环境建议调到5到10秒敏感度太高容易误判太低故障恢复慢。failover-timeout是故障转移的超时时间如果一次转移失败哨兵会重试这个值也要根据网络状况合理设置。4.2 选主规则为什么是它当选新主库当主库被判定下线哨兵集群要先选出一个哨兵作为leader来主导故障转移流程这个选举用的是分布式共识里的Raft算法思路要获得多数哨兵节点的投票。之后哨兵leader要从所有从库中选出一个新主库规则是过滤加排序。先过滤掉不满足条件的从库处于断线下线状态的、最近通信超时的、replica-priority为0的优先级为0表示永远不参与选主。然后在剩下的从库里按顺序比较优先看replica-priority值越小越优先如果优先级一样看复制偏移量偏移量越大说明从库数据越接近原主库丢失的数据越少如果偏移量也一样再比较run_id小的优先。这套规则的核心逻辑就一句话尽量找数据最新的从库上位把数据丢失量降到最低。理解了这一点你在配置多个从库时就会有意给不同从库设置不同priority比如把硬件性能最好、机房位置最合适的从库设为最低值让它更有可能被选中。4.3 脑裂问题高可用架构里最隐蔽的数据丢失哨兵架构有一个名气很大的坑叫脑裂。过程是这样的主库所在的机器并没有真正宕机只是它和哨兵之间的网络出现分区哨兵联系不上主库于是判定主库下线执行故障转移把一个从库提升为新主库。与此同时客户端还在往旧主库写数据旧主库也不知道自己已经被“废黜”仍然正常接收写入。等网络恢复旧主库发现哨兵已经选出了新主库它会作为从库去跟随新主库此时第一步就是丢弃自己的数据副本重新从新主库做全量同步。于是脑裂期间旧主库接收到的那些写请求统统丢失了。用户可能已经收到“写入成功”的响应数据却在新主库上根本不存在。这个问题的根因是主库只顾自己接收写入没有判断自己是否还“有资格”当主库。解决思路是让主库在失去和多数从库的连接时主动拒绝写入。Redis提供了两个参数作用于主库配置min-replicas-to-write 1 min-replicas-max-lag 10含义是如果主库连不上一半以上的从库或者从库的复制延迟超过10秒主库就拒绝接收写入请求。这样脑裂期间旧主库因为无法同步给从库达到lag阈值后自动拒写客户端写入失败就不会产生“假成功”的数据丢失。这个参数本身不是哨兵配置但要配合哨兵架构一起使用才更安全。配置时还要注意一个度如果只有一个从库min-replicas-to-write设为1意味着从库一挂主库也不让写了这会降低可用性。生产环境需要结合业务对数据安全与可用性的权重来综合决定。5. Cluster集群架构分布式设计的最终解法当数据量超过单机承载时必须把数据分散到多台机器上。Redis Cluster是官方推荐的分布式集群方案它的设计有不少值得展开的细节。5.1 数据分片16384个槽到底怎么算的Cluster的数据分片不直接按节点划分而是按槽。整个集群固定有16384个槽位每个key通过CRC16算法计算出一个值再对16384取模得到它所属的槽然后由负责该槽的节点来处理。槽与节点的映射关系可以在线迁移这是扩缩容的基础。为什么槽的数量是16384而不是更大的65536这是很多面试官爱问的点。一方面槽位信息需要传播到集群中的每一个节点节点之间通过Gossip协议交换状态。如果槽位是65536个心跳消息携带的槽位信息就更大网络开销显著上升而Redis集群的节点规模上限设计是1000个左右在1000个节点下16384个槽已经足够均匀分配了。另一方面16384个槽的位图用2KB字节表示65536用8KB消息体小一半多。这个数字不是拍脑袋定的是结合节点规模和数据分布粒度算出来的。hash槽机制还引出了一个经典限制Cluster模式下多个key的操作要求这些key必须落在同一个槽里否则会报CROSSSLOT错误。不同key用各自独立的名字CRC16取模后大概率分布到不同槽所以直接做mget多key操作会失败。解决方案是使用hash tag把需要关联的key放进花括号里{order}1001 {order}2001Redis只对花括号内的内容做CRC16计算这两个key就会落到同一个槽从而支持批量操作。5.2 Cluster中的高可用与一致性设计Cluster的高可用设计可以理解为“分片版主从”。每个主节点负责若干槽位每个主节点可以有若干从节点作为副本。某个主节点挂了它的从节点会自动晋升为新主节点接管相应的槽位集群其他部分不受影响。这个故障检测依赖cluster-node-timeout配置默认15秒节点间定时通过PING/PONG消息交换状态超过超时时间无法通信就会被标记为故障。要特别强调Cluster并不保证强一致。主从之间依然是异步复制主节点写入成功后返回数据同步到从节点存在延迟。如果主节点和从节点同时故障或者故障转移期间有部分数据未同步这些数据照样会丢。所以Cluster解决的瓶颈依然是容量和扩展性不是一致性。Cluster模式下Gossip协议的心跳风暴也值得关注。节点越多节点间交换的消息量越大大量节点同时故障时状态传播会引发消息洪峰。生产上不建议把集群节点做得太细碎比如1万个key一个节点不仅浪费机器消息风暴还会拖垮节点。5.3 扩缩容实操与客户端路由Redis Cluster的扩缩容比传统分库分表要灵活核心操作是槽位迁移。新增一个主节点先通过CLUSTER MEET命令把它加入集群然后用工具把其他节点上的一部分槽迁移给它。槽迁移过程中源节点和目标节点之间会搬运key这个动作不是瞬间的业务读写在迁移期间可能受到影响。客户端请求某个key时如果访问的节点不是该key所在槽的负责者节点会返回MOVED错误告知客户端这个key在哪个IP和端口上。客户端根据MOVED信息更新本地路由表后重新请求。还有一种情况是槽正在迁移中key不在源节点也不完全在目标节点这时返回ASK错误要求客户端先发送ASKING命令再请求目标节点。MOVED和ASK的区别要记牢MOVED表示槽已经永久迁移走了以后都去新节点ASK表示槽正在迁移中只是这次去目标节点查一下路由表不需要更新。扩容和缩容的顺利程度很大程度取决于槽迁移时机的选择。建议在业务低峰期执行并且分批迁移一次迁几十个槽观察一段时间的延迟和错误率再继续。6. 常见问题与排查技巧实录这部分记录的是实际运维中踩过的一些坑和应对思路不是背文档就能知道的。问题现象可能原因处理建议主从切换后写入数据丢失异步复制未同步 脑裂期间旧主库拒写配置缺失配置min-replicas-to-write和min-replicas-max-lag从库一直无法增量同步反复全量repl-backlog-size过小断连积压超出缓冲区调大repl-backlog-size调低探测超时哨兵配置了却不触发切换客户端地址错误quorum过高超时时间设置太大检查哨兵监控配置实测down-after-milliseconds读写分离读到旧数据主从延迟过大监控master_repl_offset差值敏感数据强制读主库Cluster执行mget报CROSSSLOT错误key未使用hash tag未落入同一槽业务key设计加上{关联字段}集群扩容期间部分请求失败客户端路由表未实时感知ASK/MOVED升级集群客户端支持自动重定向低峰期迁移槽排查故障有个基础顺序先用info命令看实例状态再看日志最后介入网络层确认。比如怀疑主从不同步先看不一致的数据量是恒定还是在增长增长说明复制速度跟不上写入速度需要从慢查询、网络带宽、从库磁盘IO几个方向入手。监控方面建议把INFO replication里的master_repl_offset差值、INFO cluster里的cluster_state和cluster_slots_ok、以及Redis的慢查询日志都接入告警。Redis慢查询日志的配置比较简单在redis.conf里设置slowlog-log-slower-than 10000 slowlog-max-len 128执行时间超过10毫秒的命令会被记录下来用SLOWLOG GET就能查到。很多分布式架构下的“偶发超时”最后都能从慢查询日志里找到出乎意料的元凶。最后一个经验是生产环境上Cluster集群之前最好先在测试环境完整演练一遍主节点宕机、槽迁移、从节点晋升这些场景。纸上谈兵永远无法暴露真实的问题只有实际触发了你才知道哪些参数需要调、哪些流程需要改。这段演练经验往往比任何文档都更有价值。