资讯动态

高并发系统为什么一定要用缓存?聊聊缓存的核心价值与失效风险

发布时间:2026/10/9 7:37:16 来源:尧图企业网站定制
这件事我印象特别深。去年帮朋友模拟面试问过一道看似送分词的问题高并发系统为什么一定要用缓存结果十个人里有六七个上来就答因为缓存快再问下去就只会说Redis是内存数据库比MySQL快很多。答得快但根本经不起追问。面试官不是想听你背出内存比磁盘快而是想看你有没有真正理解缓存在高并发场景下承担的职责边界它是用来扛流量的还是用来省IO的是为了降延迟还是为了保护后端很多人把缓存当加速器但高并发系统里缓存真正的价值是牺牲一部分一致性换系统的整体可用性这套权衡逻辑说不清楚基本就等同于没答对。这篇文章就围绕高并发为什么用缓存这个点把背后涉及的性能模型、缓存类型、失效风险、一致性问题一起拆开讲清楚希望能帮你在面试和实际架构里都少踩几个坑。1. 先把为什么要用缓存这件事想透缓存不是银弹而是分层权衡1.1 面试中90%的人死在哪一层我统计过身边候选人最常见的几类回答。第一类背概念型缓存就是存热点数据的读写快。第二类堆名词型可以用Redis、Memcached把数据放内存里。第三类搬方案型先查缓存没有就查数据库再回填缓存。这些回答看着方向没错但全都没有回答为什么一定需要。真正的考察点是你能不能把数据库性能瓶颈和缓存的作用建立起量化逻辑。比如你知道单台MySQL在普通SSD下QPS大概能做到2000到5000如果是一场秒杀活动瞬时流量到了10万QPS数据库要么扩容十台机器要么被拖垮。而一台Redis单节点读QPS可以到10万以上压测环境甚至更高。同样扛10万读流量用数据库需要几十台实例用缓存两三台就够了。这个数量级差异才是为什么一定要用缓存的根源。很多人的问题在于把缓存当成一个可选优化项觉得系统慢才加缓存。但在真正的超高并发架构里缓存不是优化而是基础科室。没有缓存数据库连存活都是问题。面试官想听的是这条逻辑链流量模型 → 数据库瓶颈 → 缓存承载读写 → 保障可用性。只答快就断在了前半截。1.2 从性能指标看缓存的价值我们做系统设计时习惯用两个指标衡量延迟和吞吐。延迟就是一次请求从发出到返回的时间吞吐是单位时间能处理的请求数。数据库慢查询动辄几百毫秒而Redis的GET操作平均在亚毫秒级。单看一个请求差异不大但在高并发下请求是排队处理的数据库的连接池一旦被占满后面全部阻塞延迟从线性变成指数增长。缓存的价值在于同时优化这两个指标。把数据前置到缓存后热点请求不再触发磁盘IO和SQL解析响应时间从几十毫秒降到一两毫秒同时数据库压力骤降连接池不再被打爆整体吞吐自然就稳定了。实际场景里同样的业务峰值有缓存和没缓存的系统可用性完全是两个档位。这也就是为什么很多系统加缓存不是因为想快一点而是因为不加会死。另外缓存还能削减重复计算。一些复杂聚合结果比如首页推荐列表、排行榜聚合数据如果每次都实时从多张表聚合CPU和IO开销巨大。缓存直接存储最终结果用几百字节换一次复杂计算这笔账怎么算都划算。面试时可以说到这一层说明你不只理解缓存存储数据还理解缓存承载了计算结果的复用。1.3 为什么数据库扛不住高并发线性扩展陷阱数据库不是不能扩展而是扩展很难。MySQL主从复制能解决一部分读压力但主库还是单点写并发上不去。分库分表能把压力分散但会引入分布式事务、跨节点查询、全局主键一堆麻烦。就算你愿意花成本扩机器数据库连接数、锁竞争、IO带宽也不会线性增长往往加了两倍机器性能只提升五成。缓存则简单得多。Redis天然是分布式架构客户端分片、集群扩容、读写分离都是成熟方案而且无状态加节点就能水平扩展网络带宽和内存扩展都很直接。所以高并发系统选择缓存本质上是选择了一种更容易横向扩展的存储形态把复杂的数据结构处理交给数据库把高频的简单读写交给缓存。这个理由比内存快更有说服力因为快解决的是单机效率而扩展性解决的是系统容量。我建议面试遇到这个问题时主动把话题从磁盘和内存的区别拉高到分布式系统的伸缩性。面试官会顺着问下去那缓存本身会失效怎么办缓存和数据库不一致怎么办这就自然过渡到后面的进阶问题。2. 高并发场景下缓存的三大核心作用必须答到点子上2.1 抗住峰值流量削峰填谷高并发系统最怕的不是平均流量高而是瞬时峰值。双11零点、秒杀开抢、热点新闻爆发的瞬间流量可能是平时的一百倍。数据库是按峰值设计的吗如果是那平时就是巨大的成本浪费。如果不按峰值设计那数据库就会在峰值瞬间被打崩。缓存就是那个缓冲带。大部分读请求直接打在缓存上数据库只处理缓存未命中和小部分写请求。就算瞬时流量到了百万QPS分布到缓存集群后每台压力也可控数据库依然是稳稳的几千QPS。这就像水库大坝上游洪水再猛通过泄洪闸调节下游河道不会决堤。还有一个容易被忽略的角度缓存可以让数据库的负载曲线变平滑。热点数据被缓存吸收后数据库的QPS波动明显变小不容易出现忽上忽下的状态。DBA最怕的就是负载抖动因为连接数、慢查询、主从延迟都会跟着抖。缓存把锯齿波变成了平直线这种稳定性价值比单纯加速重要得多。2.2 降低响应时间提升用户体验用户体验有个著名的三秒法则页面超过三秒打不开流失率就会明显上涨。现在移动端用户耐心更差几百毫秒的延迟都感知明显。数据库查询如果走索引也要几十毫秒复杂报表甚至几百毫秒。对用户来说每慢100毫秒转化率下降可能都是肉眼可见的。缓存的响应时间在亚毫秒级加上网络RT整体也就1到2毫秒。用户点一下按钮数据秒开这种体验是数据库直接支撑做不到的。尤其像用户会话信息、登录状态、商品详情这些高频访问数据缓存几乎是必备。而且低延迟不只是用户端感知还影响系统内部的超时策略。上游服务调用下游时都设置了超时时间比如500ms。如果每次请求数据库都要300ms那网络波动一下就直接超时。用了缓存后时间预算被大幅压缩系统对外更鲁棒。面试时如果能提到削峰 降延迟 提高系统稳定性三个维度这题基本就拿下大半了。2.3 保护后端资源控制成本高并发系统里数据库连接是稀缺资源。一个Tomcat应用默认最大连接数200一个MySQL实例最大连接数也就几百。如果所有请求都直连数据库连接瞬间被打满后续请求全部排队等待系统表现为假死。缓存把请求拦截在前面数据库连接只会分给缓存未命中的请求连接池利用率大幅度提高。从成本角度算笔账同样支撑每秒5万次读请求用Redis集群可能只需要3台8C16G的机器如果用MySQL大概需要10台以上的高配机器还不包括主从同步需要的额外磁盘。云厂商收费是按实例规格走的一年下来缓存方案能省一大笔。老板要的是低成本支撑高流量缓存正好是那个既保性能又控成本的选择。我面试时还喜欢加一句缓存可以替代一些重计算比如用位图统计UV、用ZSet做排行榜、用分布式锁做抢购防重这些都是把业务逻辑下沉到缓存层数据库更干净应用层代码也更简单。这层理解能体现出你对缓存定位的全面认知不只是存数据这么简单。3. 缓存的组合本地缓存、分布式缓存怎么选3.1 本地缓存为什么Caffeine是单机首选很多人一谈缓存就默认是Redis但高并发系统往往是多级缓存一起上。本地缓存就是JVM进程内的缓存常见有Caffeine、Guava Cache、Ehcache。为什么需要本地缓存因为Redis再快也有一次网络IO每次请求都要走网络单机RT大约1到2毫秒。但如果数据直接放在进程内存里连网络都省了RT能压到微秒级。Caffeine基于Java内部实现了类似ConcurrentHashMap的并发结构支持基于时间和容量两种过期策略还内置了TinyLFU淘汰算法能在极小内存里保留高频热点。相比Guava CacheCaffeine的命中率和并发性能都要好一截业界的默认选择基本都是它。如果系统是JVM技术栈本地缓存几乎零成本只需要注意内存占用别把堆挤爆。本地缓存的缺点是数据多副本分布在每个节点上更新一致性很难保证。所以它适合两类数据一类是几乎不变的基础配置比如字典项、黑白名单另一类是允许短时间不一致的热点内容比如商品标题、用户昵称。热点数据放在本地缓存里可以再拦掉一大半Redis请求Redis压力进一步下降。3.2 为什么Redis能扛高并发Redis单线程模型经常被误读为性能差但实际上它的性能核心在于基于内存和事件驱动。Redis把全部数据放在内存中避免了磁盘寻址和上下文切换指令执行是在内存里做哈希和链表操作速度自然快。单线程还避免了加锁竞争所有客户端请求在一个事件循环里串行处理反而不会出现多线程并发带来的性能损耗。Redis的读写吞吐据官方数据单实例可以达到10万QPS这超过了大多数业务场景的需求。配合Redis Cluster横向扩展后几十个分片轻松支撑百万级QPS。再加上它的数据结构丰富String、Hash、List、Set、ZSet很多业务逻辑可以移到Redis里实现。这就是为什么主流高并发架构最终都选择了Redis作为缓存层。面试中如果说Redis比MySQL快只算入门。更好的说法是Redis具备单机高吞吐、集群线性扩展、数据结构服务化三个特征因此能在大流量下作为统一缓存层稳定运行。顺带提一嘴持久化策略、淘汰策略、集群脑裂问题会让面试官觉得你不是停留在API层面。3.3 多级缓存本地缓存加集中式缓存的配合先查本地缓存不命中再查RedisRedis再不命中才查数据库回填时逐级写入。这是经典的L1L2缓存架构。BAT和很多高并发业务都是这么干的。为什么两级不能合在一起因为每一级缓存的成本和速度不同本地缓存最快但容量小Redis容量大但速度次之数据库容量最大但最慢。做架构就是分级承载流量。设计多级缓存时要特别注意过期TTL的层级差异避免同一份数据在各层同时失效。常见做法是本地缓存设置短时间比如30秒Redis设置长一点比如5分钟。这样即使Redis里还有数据本地过期后只需回源Redis不用压到数据库。同时更新数据时优先失效Redis再通知各节点主动清除本地缓存或等它自然过期。不过多级缓存也带来了一致性问题放大器。每次修改数据库后清缓存的操作得层层执行漏掉一层就会出现脏数据。线上常见做法是通过MQ广播通知各节点删除本地缓存这样能保证最终一致。如果只是单机部署的网关服务本地缓存加一层就够了不必过度设计。4. 光知道用缓存不够还得知道缓存失效可能让你崩得更快4.1 缓存穿透最常见的高并发杀手缓存穿透是指请求的数据在缓存和数据库中都不存在。比如查询一个不存在的用户ID每次都会绕过缓存直接打到数据库。如果这种请求大量出现数据库会承受无意义的压力。攻击者也最喜欢干这个用随机不存在的Key打到接口上直接把数据库打挂。解决穿透的思路有三类。第一类是缓存空对象把null也缓存起来并设置较短过期时间比如30到60秒。这样同一个不存在的Key后续请求就会被缓存拦截。第二类是布隆过滤器在缓存前面加一层过滤器如果判断Key不存在就直接返回但布隆过滤器有误判率可能把不存在的判断漏掉少部分也会把不存在的Key误判为存在所以通常和缓存空对象结合使用。第三类是接口层校验比如参数非法直接拒绝拦截一部分无效流量。布隆过滤器在实际项目里用得最多的地方是防止恶意攻击和避免回源穿透。它的原理是用多个哈希函数把Key映射到BitMap的一组位上查询时只要有一个位为0就说明数据一定不存在。优点是内存占用极小一亿数据只需要约120MB内存缺点是删除困难且误判存在的概率无法完全消除。我建议面试时能画出拦截链路请求进来 → 布隆过滤器 → 缓存 → 数据库。4.2 缓存击穿热点Key失效瞬间击穿和穿透一字之差但完全不是一个事。击穿是指一个热点Key正好过期一瞬间大量请求同时打到数据库。数据库压力瞬间翻倍很容易打崩。典型场景是某个爆款商品详情页缓存里不存就一分钟刚好过期那一刻来了10万请求数据库直接跪了。击穿的核心原因是单个Key的热度集中。解决方式也有几类。第一类是不设置过期时间改为后台异步更新缓存利用定时任务或订阅变更消息主动刷新。不过这样数据更新有延迟适合允许短暂不一致的场景。第二类是互斥锁缓存未命中时先拿一个分布式锁只有拿到锁的线程去查库回填其他线程等待后重新读缓存。第三类是逻辑过期存数据时在Value里额外放一个过期时间戳查到时发现逻辑过期就触发异步刷新线程先返回旧值这样不会造成数据库压力。互斥锁方案对一致性要求高逻辑过期方案对可用性和响应速度要求更严。实际项目里我倾向组合使用短TTL加互斥锁兜底再配合热点Key自动延长过期时间。面试说清楚了区别之后面试官会很自然地认为你真的在线上处理过高并发问题。4.3 缓存雪崩大面积失效如何自救雪崩比击穿更恐怖不是单个Key失效而是大量Key在同一时间段集中过期或者缓存集群整体宕机。想象一下几万个缓存Key设的TTL都是1小时某个整点同时失效流量瞬间全部压向数据库数据库可能几分钟内就挂掉然后引发连锁故障。应对雪崩可以从几个层次分别解决。第一层是TTL加随机值比如在1小时基础上加偏移量0到300秒随机让过期时间分散。第二层是缓存集群高可用Redis Cluster部署多副本客户端配置从节点读或故障自动切换保证即使部分节点挂了也能继续服务。第三层是限制回源速率对数据库增加限流降级机制比如使用Sentinel或Hystrix当数据库压力过大时直接返回兜底数据比如默认文案、旧缓存。还有一招是多级缓存天然抗雪崩本地缓存即使过期Redis那里还有Redis即使抖动本地缓存也能扛一会。平时设计系统时做个Chaos Monkey演练随机停止Redis节点观察应用是否还能稳定运行这种主动故障注入比事后救火要靠谱得多。5. 缓存一致性问题面试官真正想听到的为什么5.1 一致性困境强一致 vs 最终一致为什么缓存后数据反而更难保证一致因为同一份数据现在有两个存储位置数据库一份缓存一份。更新时要么先更新数据库再更新缓存要么先更新缓存再更新数据库任何一个顺序搞反或者失败都会导致两个地方的数据不一样。强一致性需要引入分布式事务比如两阶段提交或者Seata全局锁代价非常高昂性能也大打折扣这和高并发场景追求的快速响应是矛盾的。所以在缓存架构里我们默认选择最终一致性允许数据在短暂的时间窗口内不一致通过异步补偿机制最终收敛。缓存过期时间本身就是一种最终一致性机制过期后重新回源数据库数据自然就一致了。面试官问到一致性其实想看你能不能接受工程上的权衡。如果能说清楚缓存必然存在不一致窗口需要通过过期时间和删除策略来控制窗口大小这个认知就很成熟了。如果还执着于强一致说明你没真正理解缓存的存在意义。5.2 Cache Aside 模式为什么是默认选择业界最常见的缓存读写模式是Cache Aside也叫旁路缓存。读的时候先读缓存缓存没有就读数据库然后把数据写入缓存。写的时候先更新数据库然后删除缓存或者更新缓存。之所以选择删除缓存而不是更新缓存是为了避免写缓存是反并发的问题。试想两个并发请求同时更新同一条数据一个改为A一个改为B如果都是先改数据库再更新缓存数据库最终是B缓存可能被后到的A覆盖导致DB和缓存不一致。更稳的做法是更新数据库后直接删除缓存下一次读时再回源数据库重新写入。这样即使删除失败最多也就是多一次回源数据库和缓存不一致的时间窗口被控制在一个TTL周期内。Cache Aside模式也有坑极端的并发场景下先删除缓存再更新数据库如果删除缓存后正好有个读请求进来发现缓存没有就回源数据库读到了旧数据再回填缓存之后就变成缓存旧、库新的状态了。这个问题的标准解法是延迟双删先删除缓存、更新数据库、隔几百毫秒再删除一次缓存。删除多一次的成本很低但能把并发窗口补上。5.3 双写不一致与延迟双删实际开发中很多人喜欢用更新缓存而不是删除缓存理由是省一次回源。但如果更新顺序控制不好非常容易产生脏数据。比如线程A更新DB为10线程B更新DB为20B先更新缓存为20A后更新缓存为10最终缓存是10而DB是20彻底不一致。这类问题在高并发下出现概率很高而且难以复现。所以主流的做法都是DB更新成功后删缓存而不是更新缓存。删除缓存是幂等操作删多少次结果都一样。同时配合延迟双删可以解决更新数据库期间读请求回填旧数据的问题。延迟时间一般设为业务读请求内部完成回源写入所需时间的1到2倍比如200ms到500ms。这里我不能给一个绝对标准因为不同业务的查询耗时不同但可以通过压测或日志分析统计回源写入的P99耗时来设定。如果对一致性要求更高还可以通过binlog订阅来做异步删除。比如Canal监听MySQL的binlog数据变更后自动把对应缓存Key删除既不侵入业务代码也不依赖延迟时间还能保证最终一致。这也是很多大厂在治理缓存一致性时的标准做法。面试如果能说出Canal方案的适用场景会显得实战经验很足。6. 我眼中正确回答的满分框架6.1 把用缓存上升到系统设计层面面试官问为什么一定要用缓存本质上是在考你的架构决策能力。我的建议是把回答组织成问题-分析-方案-代价四步。第一步说明问题高并发下数据库是瓶颈连接数有限、磁盘IO有限、无法线性扩展。第二步给出分析真实流量分布中热点数据占比高比如10%的热点Key占到了90%的访问量把热点前置到内存能够显著削减数据库压力。第三步提出方案构建本地缓存Redis多级缓存写数据时Cache Aside失效缓存读数据时回源重建同时用布隆过滤器防穿透、随机TTL防雪崩、分布式锁防击穿。第四步点明代价缓存会引入数据一致性问题、缓存成本和运维成本需要权衡业务容忍度。这样一套回答不仅回答了为什么还把高级工程师应该具备的系统设计方法论全展示出来了。很多人答不好是因为只给出了结论没有给出推导过程。先承认瓶颈再通过量化分析最后落到落地方案面试官想听到的是这套思考链条。6.2 回答案例与措辞建议假设面试官问的就是原题可以这样组织语言高并发下数据库的QPS天花板通常在几千到一万但业务峰值可能达到十万级直接打库会导致连接池耗尽、响应超时甚至雪崩。缓存通过把热点数据放到内存中承担了绝大部分读流量让数据库只处理缓存未命中和写请求。这样不仅降低响应时间更重要的是削峰填谷保护了后端数据库的稳定性。同时缓存集群可以进行横向扩展用较少的机器成本支撑更大的并发量。代价是数据可能短暂不一致以及缓存失效时可能引发穿透、击穿、雪崩所以需要配合过期策略、空值缓存、布隆过滤器和多级缓存来解决。这段话大概150字把为什么、怎么做、代价是什么都压缩进去了。面试时不要背但可以按这个思路自然表达。关键是用词不要太大路货别总说快要说吞吐能力差距连接池耗尽削峰填谷可用性保障这些工程词汇。6.3 面试官可能追问的延伸点答完主问题后面试官极有可能顺着问三个问题第一缓存和数据库不一致怎么解决第二缓存穿透、击穿、雪崩分别是什么以及应对第三Redis为什么快所以准备这道题时要一并把这些关联问题过一遍。不要只是背题要把三者的关系串起来。我建议方法还是从一个真实场景出发把这几个问题全部串完。比如设计一个商品详情页缓存用多级缓存查询先走本地再到Redis布隆过滤器挡掉不存在的ID热Key用逻辑过期解决击穿TTL加随机值解决雪崩更新商品时删缓存并延迟双删。这一个场景讲五分钟面试官能直观看到你的设计能力比孤立地背概念强很多。最后还可以带一句我们线上缓存命中率基本在95%以上数据库QPS只有缓存QPS的几十分之一。有数据佐证的话回答会更有说服力。如果没有真实数据可以说按行业经验合理设计下命中率通常能达到95%以上。7. 缓存治理线上常见的坑与排查思路7.1 内存膨胀与淘汰策略缓存装数据很容易但内存不是无限的。很多团队出过这种问题Redis内存涨到90%以上触发maxmemory策略开始无情淘汰Key结果缓存命中率骤降大量请求回源数据库被打垮。根本原因是上线时没规划容量也没想清楚淘汰策略。Redis的淘汰策略有noeviction、allkeys-lru、volatile-lru、allkeys-random等。业务缓存一般用allkeys-lru优先淘汰最少使用的Key配合合理的maxmemory比例。还要监控内存增长趋势通过Redis的INFO命令定期采集used_memory设置告警阈值比如80%就要扩容或迁移热点Key。另一个坑是Key过期扫描消耗CPU。Redis清理过期Key是在主线程里做的如果过期Key太多或者集中在一个时间点会产生明显卡顿。线上经验是避免把所有Key设置为同一个整点过期用随机TTL错峰。还有一个细节大Key删除要特别小心几MB甚至更大的Value直接DEL会引起阻塞应该用unlink异步删除。这些都是运维层非常实在的注意事项。7.2 热Key监控与处理高并发系统最怕的是热点集中某个明星事件突然把单个商品或用户ID的访问量推高百倍一个Redis分片被打满拉低整个集群性能。热Key问题几乎每个大流量的团队都遭遇过。处理分两步一是监控二是拆散。监控方面可以利用Redis的hotkey参数或者使用lettuce/redisson客户端自带的热Key统计也可以在做命令代理层进行计数把访问频率TopN的Key实时上报。更简单的办法是定期扫描RDB文件统计Key的出现频率。发现热Key后可以用本地缓存拦截掉大部分流量或者对Key做后缀分片比如把同一个Key的数据复制到多个分片加上随机后缀来查询这样压力被分散到不同节点上。热Key还会引发一个连锁问题大量读同时并发时对Redis的单个Key执行GETRedis单线程虽然能扛但网络带宽会成为瓶颈。大Value最好拆成多个小Value或者用Hash结构分字段存储减少一次网络传输的数据量。平时养成把大Key治理掉的好习惯线上稳定性会有明显提升。7.3 缓存故障的应急三板斧缓存不是不会挂关键是挂了你要有预案。很多公司经历过Redis集群整体故障然后全站接口超时的惨案。我见过的成功经验有三板斧。第一板斧是降级开关。在接口层面做一个动态配置比如通过配置中心下发开关当缓存故障时直接跳过缓存返回默认数据或走数据库但不能崩溃。要注意的是如果缓存已经挂了回源数据库会造成巨大的流量冲击所以降级QPS也要限制比如只放行20%流量回源其余返回兜底内容。第二板斧是多级缓存兜底即使Redis故障每个节点上的本地缓存还能扛一段时间。第三板斧是快速隔离恢复比如对Redis集群做读写分离故障时切到从节点依靠Sentinel或Cluster自身的failover机制尽量缩短不可用时间。线上排障的常用套路是先看缓存命中率是否下降再看Redis的INFO里的connected_clients、mem_fragmentation_ratio、latency指标最后结合慢查询日志确定是不是某个大Key或热Key拖慢了整个实例。我见过排查三天定位不到的问题最后发现是慢查询日志里有一条keys *命令直接把Redis阻塞了几秒。所以在生产环境要绝对禁用keys用scan代替。这条经验值得刻在工位上。最后的个人体会把高并发为什么用缓存这个问题想透彻之后再看很多设计突然就通了原来并不是所有数据都适合进缓存只有读多写少、热点集中、允许短暂过期的数据才是缓存的最佳目标也不是只要用了缓存就能高枕无忧缓存失效带来的穿透、击穿、雪崩每一个都可能放大故障。我自己在经历了线上Redis误删导致全站商品信息消失3分钟的惨痛教训后才真正理解缓存是必须的但绝不是用来偷懒的。每次设计方案时我都会反问自己如果缓存全挂了系统还能撑住吗如果缓存和数据库不一致业务能容忍多久这两个问题想清楚比背十种缓存策略都有用。

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

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

免费获取报价 →
↑