
其实很多朋友第一次看到“设计一个分布式缓存系统”这种题第一反应是分布式缓存不就是 Redis 集群吗把 Redis Cluster 搭起来客户端连上去好像就完事了。但在真正的系统设计面试或实际架构评审里面试官想听的不是“用哪个开源组件”而是你有没有把缓存当做一个独立系统来思考数据怎么分片、请求怎么路由、缓存和数据库的一致性怎么保证、节点挂了怎么办、热点数据会不会把集群打爆。这背后的能力才是架构师和普通开发者的分水岭。这篇文章我打算按一个完整的模拟面试记录来写从需求澄清开始到数据分片、一致性、高可用、监控治理最后给出一套可以直接套用的答题框架和参考答案。不管你是准备面试还是公司内部要做缓存中间件选型这套思路都能直接用上。我会把设计逻辑、常见坑、以及一些面试官喜欢追问的细节都摊开来讲。1. 开始之前先把题目边界划清楚一上来就谈 Redis、Memcached、一致性哈希这是新手最容易犯的错。系统设计题的第一原则是没有需求的设计就是耍流氓。同样叫“分布式缓存系统”你做的是一个几百 QPS 的内部工具还是要支撑百万 QPS 的电商大促设计方案完全不一样。所以拿到题目后的第一件事不是画架构图而是“问问题、定边界、立指标”。1.1 功能需求和非功能需求怎么拆功能需求一般来说很直观读缓存、写缓存、设置过期时间、支持删除 key。只要你做的是通用缓存基本就是这四类操作。如果有更细的业务场景比如需要支持按前缀批量扫描、需要支持事务、需要支持 Lua 脚本那就直接把复杂度拉高了一个等级。正常系统设计面试题默认不需要把这些高级功能全做进去提一嘴即可。非功能需求才是设计的核心。你需要搞清楚这几个数值数据总量单机内存能放下吗比如 100 GB 的数据单机 64 GB 内存就放不下必须分片。QPS 和延迟读多写少还是读写均衡平均延迟要求是多少p99 延迟要求又是多少。可用性要求允许缓存节点故障吗故障后是降级到数据库还是需要自动切换一致性要求缓存和数据库之间是强一致还是最终一致多副本之间允许短暂不一致吗把这些数字拿到手你才能决定架构的复杂程度。比如说数据量只有 10 GB读 QPS 只有 5 万那我觉得单机 Redis 加一个 AOF 持久化就够用了非要上一套代理分片集群反而增加了运维负担。但如果是 1 TB 数据、读 QPS 500 万、要求故障 1 分钟内自动恢复那你就必须考虑分片、副本、多机房容灾这些重机制。1.2 一张分层架构图把你和“只会用 Redis”的人区分开分布式缓存系统从宏观上可以分成四层客户端层SDK、连接池、路由算法、故障感知。接入/代理层处理协议解析、请求路由、灰度发布、限流。缓存存储层真正存数据的节点每个节点可以是单机缓存引擎也可以带副本。治理/控制层配置管理、监控告警、数据迁移、故障切换。很多面试者上来就画一堆 Redis Circle然后指着说“这就是分布式缓存”。但真正有经验的人会说客户端 SDK 负责一致性哈希路由代理层负责动态感知节点状态存储层每个分片采用主从结构主节点挂了从节点自动提升控制面负责把路由配置推给客户端或代理。这一套完整链路讲完面试官就知道你不只是在背八股文。我自己实际做过一个日请求量几十亿的缓存平台最后落地的架构就是“代理层 存储集群 配置中心”的模式。核心原因很简单客户端直接访问存储节点看起来省了一层开销但当你有几十个业务方接入时客户端升级带来的沟通成本、故障排查成本高得惊人。代理层虽然增加了一跳网络开销但换来的是统一管控和快速灰度这笔买卖非常划算。2. 数据分片与路由缓存系统的地基分片是分布式系统最核心的机制没有之一。为什么要分片因为单机容量和单机吞吐量都有上限。100 GB 数据放在一台机器上内存不够即使内存够了单机处理 100 万 QPS 也不现实。分片就是把数据按照某种规则拆到多台机器上让每台机器只承担一部分数据和一部分流量。2.1 哈希取模和一致性哈希到底怎么选最简单的分片方式是hash(key) % N。这个方案实现简单但有一个致命的缺点当你把节点数从 N 扩容到 N1 时绝大部分 key 的映射关系都会发生变化。这意味着缓存全部失效大量的请求会穿透到数据库瞬间压力翻倍这种场景我们一般叫“缓存雪崩”。所以现实里我用得最多的是一致性哈希。它的核心思想是把哈希值空间组织成一个环节点也映射到环上每个 key 从它的哈希位置出发顺时针找到第一个节点。这样新增或删除节点时只影响这个节点附近一小段的数据其他 key 的映射关系保持不变。一致性哈希也有一个经典问题节点数少的时候hash 环上的节点分布不均会出现数据倾斜。解决办法是给每个物理节点加上很多“虚拟节点”让它们在环上均匀散开。虚拟节点数量一般建议 100200 个具体要看集群规模。比如一个物理节点有 150 个虚拟节点集群有 10 台机器那整个环上就有 1500 个虚拟节点分布已经比较均匀了。2.2 虚拟节点、数据迁移与扩容缩容的真实代价面试官特别喜欢接着问一致性哈希只是让“需要迁移的数据变少了”但迁移过程具体怎么做这里很多人的答案会含糊掉。我讲一个实际方案假设原来有 4 个节点环上的 key 分布在 A、B、C、D 上。现在新增一个节点 EE 的虚拟节点落到环上之后只有它顺时针方向到前一个节点之间的 key 需要迁移。迁移不是一次性拷完而是通过“双读双写”的方式平滑完成先让 E 节点开始接收新写入的 key同时后台任务把旧节点上属于 E 区间的数据拷贝到 E拷贝过程中如果客户端查到 E 没有数据就回源到旧节点然后再异步回填到 E。等数据全部拷贝完再把路由配置正式切到 E。这里有一个很关键的细节迁移期间数据不一致怎么办如果业务对一致性要求不高双读双写加异步回填就够了。如果要求比较高那就需要引入版本号或者用消息队列保证更新顺序。这套扩展逻辑放到面试里说面试官立刻知道你真的处理过线上扩容。2.3 路由方式的选择SDK 直连、Proxy 代理还是服务端跳转分片方案定下来接下来要考虑路由在哪一层做。常见的有三种客户端直连路由SDK 里实现一致性哈希客户端直接连对应节点。优点是性能最好、少一跳缺点是 SDK 升级困难Java、Go、Python 每个语言都要维护一套出问题难以统一处理。代理层路由客户端只连 Proxy由 Proxy 做哈希路由。优点是客户端极简协议转换、限流、监控都可以在 Proxy 层统一做缺点是增加一跳网络开销Proxy 可能成为性能和可用性瓶颈。服务端跳转类似 Redis Cluster 的做法客户端可以连接任意节点如果数据不在当前节点节点返回 MOVED 重定向。让客户端再次请求正确节点。这个方案兼顾了一部分灵活性和性能但对客户端的协议栈要求比较高。我自己的经验是中小团队用客户端直连最省钱但团队规模超过 20 人、业务方超过 5 个之后Proxy 模式的优势会越来越明显。因为你能在 Proxy 上做所有策略的集中管控而不是求着每个业务方升级 SDK。你也不用担心 Proxy 的性能因为 Proxy 本身可以水平扩展前面加一层负载均衡即可。3. 缓存更新策略和一致性保障这部分的坑最密集很多架构师能把分片和高可用讲得头头是道结果一聊到“缓存和数据库怎么保持一致”就开始含糊了。这一块是分布式缓存系统里最容易出问题的地方也是最值得花时间深挖的部分。3.1 Cache Aside、Read Through、Write Back各自的使用场景先讲业界最常见的三种模式Cache Aside 旁路缓存。读的时候先读缓存缓存没有就查数据库然后把结果写回缓存写的时候先更新数据库然后删除缓存或者更新缓存。这个模式最大的优点是实现简单适合大多数业务场景。最大的坑是如果先更新数据库再更新缓存两个操作不是原子的可能出现缓存里是旧值、数据库里是新值的不一致。所以工程上更推荐“更新数据库后删除缓存”下一次读的时候再回填。这个方式也被称为 lazy loading。Read Through 读穿透。缓存系统自己负责从数据库加载数据业务方只和缓存交互。这个模式适合数据访问模式比较稳定、可以预热的场景。但实现起来更复杂因为缓存引擎需要内置数据源接口。Write Back 写回。所有写操作只写缓存由后台异步批量刷到数据库。这个模式性能极好适合写多读少、允许数据丢失的场景比如计数、点赞、埋点。但缺点是万一缓存节点宕机数据就永久丢了所以使用门槛很高。真实业务中最常用的就是 Cache Aside 删除缓存。有一个很经典的问题“到底是先删缓存再更新数据库还是先更新数据库再删缓存”正确答案在多数并发场景下是先更新数据库再删除缓存。为什么因为先删缓存、再更新数据库会导致删完缓存后还没更新数据库的间隙另一个请求把旧数据读回缓存数据库更新后缓存成了永远不一致的旧值。而先更新数据库再删缓存虽然删缓存失败的概率也存在但通常可以用延迟双删或者订阅 binlog 来兜底。3.2 缓存穿透、击穿、雪崩一个表讲清楚缓存设计面试中 90% 的追问都会围绕这三个问题展开。我用一个表格给你梳理清楚然后在后面一步步说对策。问题表象根本原因典型对策缓存穿透大量请求查询不存在的 key直接打到数据库缓存无法命中不存在的 key布隆过滤器、空值缓存、参数校验缓存击穿某个热点 key 过期大量并发请求同时打到数据库单个热点 key 过期瞬间没有缓存保护互斥锁重建缓存、逻辑过期、热点 key 不过期缓存雪崩大批 key 同时过期数据库压力骤增大量 key 设成同一个过期时间过期时间加随机值、多级缓存、熔断限流缓存穿透最容易理解。用户不停请求一个 id 为-1或者不存在的商品每次请求都穿透到数据库。最简单的办法是缓存这个 null 值设置一个较短的过期时间比如 5 分钟。更好的方案是布隆过滤器把所有存在的 key 先放到布隆过滤器里请求来了先检查布隆过滤器不存在直接返回不再访问缓存和数据库。缓存击穿最常见的修复方式是用互斥锁。在 key 过期的那一刻只允许一个请求去数据库加载并重建缓存其他请求先等待。实现上可以用 Redis 的SETNX也可以用进程内锁。还有一个思路是“逻辑过期”不给 key 设物理过期时间而是在 value 里存一个逻辑过期时间戳读取时发现逻辑过期后异步去刷新缓存同时返回旧值。这个方案在秒杀场景特别实用可以避免瞬时锁等待。缓存雪崩的核心修复是“过期时间的随机化”。把过期时间从固定值改成TTL random(0, 300)秒让同一批 key 不集中在同一时刻过期。另外可以做多级缓存本地缓存作为一级缓存Redis 作为二级缓存即使 Redis 里大量 key 过期本地缓存仍然能挡住相当一部分请求。3.3 多副本数据一致性别想着强一致分布式缓存的多个副本之间以及缓存系统和数据库之间如果要做到强一致代价非常大。比如你得引入 Paxos/Raft 这样的共识协议每次写入都要多数派确认延迟会明显增加吞吐量也会下降。而缓存这种场景绝大多数业务其实是可以容忍短时间不一致的。所以在设计阶段我的建议是给缓存和副本之间定义清楚“最终一致”的容忍窗口。比如商品库存、交易金额这种数据不允许缓存不一致太久那你可以采用“更新数据库后立即删缓存 订阅数据库 binlog 异步重试删缓存”的兜底方案如果是用户头像、文章浏览量这种数据就算缓存里旧个几秒钟业务上也没人感知那异步刷新就足够了。这里有个非常重要的细节不要为了追求一致性把事务和分布式事务引到缓存系统里来。缓存系统不是数据库它不承诺事务性。如果你发现自己正在设计一个需要跨节点强一致提交的缓存接口那你大概率是把需求搞错了应该重新思考业务架构。4. 高可用架构节点宕机之后缓存系统还能撑住吗聊完数据一致性接下来是可用性设计。分布式缓存系统的高可用需要回答三个问题节点故障怎么感知数据副本怎么切换整体流量怎么防护4.1 副本机制与自动故障转移单机缓存挂了如果后面直接是数据库流量冲击会非常大。所以常规做法是每个分片配一个主节点和至少一个从节点主节点负责读写从节点负责备份。主节点宕机后从节点自动升级为新的主节点。这里的关键是“自动升级”如何实现。很多团队直接把 Redis Sentinel 或 Redis Cluster 的故障转移机制拿过来用确实是省事的选择。但如果要自己实现这套机制那必须有一个分布式协调组件负责心跳检测、选主和配置通知。比如用 etcd/zookeeper每个节点向协调中心注册并上报心跳协调中心发现主节点心跳超时后在从节点中发起选主然后更新路由配置。有一个容易被忽略的细节故障转移时已经不完整的数据可能会丢一部分。如果主从复制是异步的主节点宕机前有一部分最新写入还没同步到从节点从节点提升为主节点后这部分数据就丢了。严格来说这是可用性和一致性之间的权衡。如果你希望数据不丢就得用同步复制或者半同步复制但这会拖慢写入延迟。所以要在设计文档里明确跟你团队说清楚缓存系统重启或故障可能丢失最近的少量数据这是可接受范围。4.2 多机房部署与容灾切换当缓存系统要支撑核心业务时单机房是不够的。我参与的缓存平台采用的是“同城主备 异地只读”的部署方式。主机房承担读写流量备机房保持热备数据通过异步通道同步如果主机房整体故障把流量切到备机房。这个过程中数据可能会落后一小段时间所以需要业务侧接受“秒级延迟”。多机房间的数据同步有几种方式基于 binlog 同步、基于消息队列同步、或者直接在缓存引擎上开启主从复制。具体选哪种取决于你允许的同步延迟和运维复杂度。但有一个原则是不变的控制面要做全局路由切换而不是让客户端自动探测机房。因为客户端自动探测很容易出现“脑裂”两个机房都以为自己应该是主写冲突很难收拾。4.3 优雅降级、熔断和限流保护数据库的最后防线缓存系统无论设计得多健壮也要考虑最坏情况。当缓存大面积不可用时系统不能直接把所有请求透传到数据库。所以设计里必须包含降级开关。正常情况下我们给缓存配了很高的 QPS但数据库能承受的 QPS 是有限的比如只能扛 1 万缓存挂掉后端瞬间来了 50 万 QPS数据库 5 秒钟就会被打挂。降级方案一般有三层本地缓存兜底Java 进程里用 Caffeine/Guava 做一个小的本地缓存作为分布式缓存的“影子部队”。分布式缓存挂了本地缓存还能挡住 80% 的读请求。接口级熔断当分布式缓存的错误率超过阈值比如 30%SDK 自动熔断一段时间不再请求缓存直接走数据库但只允许一小部分请求通过避免数据库被打挂。最小化降级对非核心接口直接返回默认值比如推荐流、热门榜单这种直接返回空页或旧页面等缓存恢复后再补全。这些兜底策略要在设计文档里写清楚尤其是熔断阈值和恢复策略。很多人只做熔断不做恢复结果缓存恢复正常了SDK 里还一直熔断业务受损时间被白白拉长。正确做法是在熔断后引入半开状态放少量请求试探如果成功率达到预期就把熔断器关闭。5. 监控、容量规划和日常运维设计文档里最容易漏的部分面试官如果只考到“高可用”那还只是一个中级系统设计的水平。真正拉开差距的是你有没有考虑到监控和运维。一个设计再完美的分布式缓存系统如果不做监控、不做容量规划、不治理大 key 和热 key上线半年后一定是一堆事故等着你。5.1 监控指标不能只看命中率很多团队的缓存监控只盯着命中率确实命中率是最直观的指标但远远不够。我建议至少要监控以下四类性能指标平均延迟、p99 延迟、p999 延迟、单节点 QPS、网络带宽。容量指标内存使用率、key 数量、大 key 数量、内存碎片率。错误指标超时率、异常次数、连接拒绝数、主从复制延迟。业务指标命中率、热点 key 访问分布、未命中后数据库回源量。这里我想重点强调一下“回源 QPS”这个指标。很多系统命中率看起来很高99% 都是缓存命中的但剩下的 1% 如果是一万个 key 同时过期瞬间回源的 QPS 也可能把数据库打爆。所以监控里不但要统计回源总量还要统计回源 QPS 的瞬时峰值以及回源的 TOP key。5.2 大 Key 和热 Key 是缓存系统的两大杀手大 key指的是单个 key 对应的 value 特别大比如一个 key 里塞了一个几 MB 的 JSON 列表。大 key 的危害在于网络传输耗时高、单次请求占用内存多、数据迁移时容易阻塞线程。治理方式是从编码上拆分成多个小 key或者改用 Hash 结构。如果实在拆不了可以把大 value 压缩后再存并设计好序列化协议。热 key指的是某一个 key 在短时间内被超高并发访问比如双十一的爆款商品 key。大量请求打到一个分片上导致单个 Redis 节点的 CPU 达到瓶颈就算你做了分片也解决不了因为热点 key 永远只会落在其中一个分片。常见的解法是本地缓存 热点 key 识别 多副本冗余。比如把热点 key 复制成key#1、key#2、key#3多个副本分散到不同分片上客户端随机选一个副本读。这个方法会带来一致性问题所以一般只对允许短时间不一致的读多写少场景使用。5.3 容量规划别等内存满了才想起来换配置我见过很多线上事故都是内存打满引发的。缓存系统不像数据库那样有完善的磁盘容量管理Redis 默认就是“全内存”。如果不做内存上限控制某些业务方一条大 key 就可能把整台机器内存耗尽。所以在容量规划上有几个点需要提前定好单机内存上限比如 Redis 的maxmemory必须设置不能是无限大。淘汰策略内存满时用allkeys-lru、volatile-lru还是allkeys-random要提前想清楚。一般业务场景用 LRU 就够了如果你的 key 基本都会设置过期时间用volatile-lru更合适。数据增量预估根据业务增长速度定期评估未来 3 个月、6 个月的容量。比如当前数据量 200 GB每月增长 20%那半年后就到了 440 GB是扩容还是优化数据结构要提前规划。5.4 连接数与内存碎片细节里藏着事故还有一个容易踩坑的地方是连接数。不管是 Redis 还是自研缓存引擎单机连接数都是有上限的。当客户端连接池配置不合理时高峰期可能出现大量连接超时。我通常建议客户端连接池大小设置为(业务单机 QPS / 单连接承载 QPS) * 节点数还要留出 20% 冗余。内存碎片也是一个常被忽略的点。Redis 等内存型存储频繁增删 key 后内存碎片率高的时候可用内存看起来还有但实际可能分配不出连续内存。Redis 4.0 以上支持自动碎片整理但还是要有监控碎片率超过 1.5 时就需要人工介入整理或者重启节点。6. 从拿到题目到讲完答案一套可直接套用的面试框架这一节当作整套设计思路的“参考答案”来用。我按照系统设计面试的 30 分钟节奏来划分你拿去就能练。6.1 30分钟答题节奏与提纲时间段做什么你要说的重点0-3 分钟澄清需求和指标明确数据量、QPS、可用性、一致性、延迟要求3-8 分钟给出整体架构客户端 代理 存储分片 控制面四层结构8-15 分钟深入数据分片和路由一致性哈希、虚拟节点、扩容迁移、路由方式对比15-22 分钟深入一致性和缓存策略Cache Aside、过期策略、穿透/击穿/雪崩解决22-27 分钟深入高可用和容灾主从切换、多机房、熔断降级、数据同步27-30 分钟总结和补充监控指标、容量规划、灰度发布、大 key/热 key 治理这套节奏的核心是把主动权握在自己手里不要等面试官逐点追问。每讲到一层顺手把下一层的引子抛出去比如讲一致性哈希时主动提一句“扩容的时候我用虚拟节点来做数据迁移后面可以展开细讲”面试官大概率会顺着你引导的方向提问。6.2 高频追问与应对思路问“如果缓存和数据库数据不一致怎么解决”答先分清是“缓存与数据库不一致”还是“缓存多副本不一致”。前者用 Cache Aside 删除缓存 binlog 兜底后者用版本号或让多副本从同一个数据源同步。关键是明确容忍窗口不要为了强一致牺牲性能。问“一致性哈希能完全避免数据迁移吗”答不能。一致性哈希减少的是迁移范围但迁移过程依然存在。实际方案是双读双写、后台迁移、逐步切流。问“这个系统能支持千万 QPS 吗”答能但要看数据热点分布和资源预算。先横向分片解决容量和单点 QPS再用本地缓存解决热 key最后用多机房就近读取解决跨地域延迟。如果千万 QPS 同时集中在一个 key 上那要先解决业务热点而不是单纯加机器。问“缓存抖动/超时怎么排查”答先看监控里是单节点抖动还是全局抖动。单节点看大 key、热 key、内存碎片、fork 持久化阻塞全局看网络、连接池、代理层瓶颈和数据迁移。我习惯从“先定位是不是单点问题再看是不是链路问题”的方向排查。6.3 面试答题中的三个“加分项”第一个加分项是说清楚“缓存不是数据库的遮羞布”。很多人把缓存设计成数据库的高可用挡箭牌一旦缓存挂了数据库就暴露在流量洪峰里。但真正好的设计是“数据库即使被大量流量打到也应该有能力降级和自我保护”。你在设计缓存时应该顺带设计数据库限流和降级策略。第二个加分项是主动聊“成本”。分布式缓存的成本有三个机器成本、运维成本、一致性成本。你可以说如果数据量不大我会先用单机缓存 本地缓存不引入分片如果业务线多了我会优先选 Proxy 模式降低业务接入成本。这样面试官会觉得你不是只会堆技术组件而是会算账。第三个加分项是拿真实事故来说话。比如你在讲热 key 治理的时候抛出一个具体案例“之前大促的时候出现过用户中心的一个 uid key 被恶意刷量单分片 CPU 被打到 95%最后通过本地缓存 热点副本解决了。”真实案例永远比理论描述更有说服力。7. 写在最后的一点私货说完这么多理论最后分享一个我踩过很多次坑后才明白的道理分布式缓存系统设计的真正难点不在于某一个技术细节而在于所有细节之间的相互制约。一致性哈希做好了扩容简单了但热 key 仍然没办法只靠分片解决主从复制做好了可用性提升了但数据可能短时间丢失本地缓存加多了延迟降下来了但数据不一致的概率又上去了。每次做完一个设计我都会问自己一个问题如果缓存系统突然全部宕机 10 分钟我的业务会怎样如果这个问题的答案是“数据库会被打死”那说明这个设计里缺少降级和兜底如果答案是“用户无感知”那说明冗余做得到位但成本可能偏高。这个问题的答案没有对错它只取决于你们业务对可用性的真实要求。在实际动手之前我建议你先拿一张纸把自己的核心场景写下来预估容量、QPS、一致性要求、可用性要求、运维团队规模。想清楚这个前提再回头看我前面讲的分片、共识、一致性、高可用你会发现每一步都变成了必然的选择。分布式缓存不是一套模板走天下它是一道“根据需求推导架构”的题而推导的过程就是你和普通使用者之间最大的区别。