
1. 这不是题库是缓存系统的实战解剖刀“10道不得不会的缓存面试题”——看到这个标题别急着去背答案。我带过三十多个Java后端团队每年筛简历、面人、做技术终审见过太多人把“缓存”当成一个名词来记Redis是缓存、本地Map是缓存、浏览器有缓存……但一问“为什么用它”“为什么这么用”“换种场景还成立吗”立刻卡壳。这10道题本质不是考你背了多少定义而是检验你有没有亲手调过缓存穿透的熔断阈值、有没有在凌晨三点盯着监控看缓存击穿后的数据库CPU飙升曲线、有没有因为没设好TTL导致整条订单链路雪崩过。缓存不是锦上添花的装饰品它是高并发系统里最锋利也最危险的那把双刃剑。你写的每行代码只要涉及“查数据库前先查缓存”就已站在了性能与一致性的悬崖边上。这10道题覆盖的是真实生产环境里高频踩坑的5大核心战场缓存穿透、缓存击穿、缓存雪崩、缓存一致性、缓存设计权衡。它们背后没有标准答案只有具体场景下的取舍逻辑——比如MyBatis二级缓存为什么默认不开启Spring三级缓存里为什么第三级要用ObjectFactory而不是直接放Bean实例Redis做分布式锁时setnxexpire为什么不行而要用Lua脚本原子操作这些都不是“知识点”而是你上线前必须拍着胸脯说“我理清楚了”的决策点。如果你正在准备Java/后端/全栈面试这10道题就是你的能力分水岭。背下来能过初面但真正理解底层机制、能画出数据流向图、能说出每种方案在QPS 5000和QPS 5万时的不同表现才能拿到高级岗的offer。更关键的是这些题的答案直接对应着你未来三个月要写的生产代码——比如电商秒杀场景下商品详情页的缓存更新策略就决定了你能不能扛住流量洪峰而不拖垮数据库。所以别把它当面试题把它当一份缓存系统的设计说明书来读。下面我们就一道一道拆不讲虚的只讲线上真刀真枪用过的逻辑、参数、陷阱和补救措施。2. 缓存穿透空值攻击下的第一道防线2.1 什么是缓存穿透它比你想的更致命缓存穿透不是“缓存没命中”而是“请求的数据根本不存在却反复穿透缓存直击数据库”。举个最典型的例子用户ID是自增主键有人恶意构造id-1、id9999999999这种明显不存在的值发起高频请求。缓存里查不到就打到DBDB查不到返回null下次再查还是null……结果是缓存形同虚设所有请求都压在数据库上而数据库还在傻乎乎地执行SELECT * FROM user WHERE id ? —— 这种查询连索引都用不上全表扫描起步。我亲眼见过一个订单服务被爬虫用脚本扫了10分钟不存在的订单号MySQL连接数瞬间打满连带整个支付链路超时。注意这不是DDoS而是精准打击。它的危害在于攻击成本极低发个HTTP请求就行防御成本极高数据库扛不住。而且它不依赖流量大小哪怕每秒只来10个穿透请求如果数据库本身负载已经80%这10个请求就足以触发雪崩。2.2 布隆过滤器空间换时间的终极选择布隆过滤器Bloom Filter是解决缓存穿透的工业级标准方案。它的核心思想很朴素在请求到达缓存之前先用一个极小的内存结构快速判断“这个key绝对不存在”。如果布隆过滤器说“不存在”那就直接返回空连缓存都不查如果说“可能存在”再走正常缓存-数据库流程。为什么选它因为它完美匹配穿透场景的三个硬需求极低内存占用一个能存1亿个key的布隆过滤器内存占用不到2MB。对比HashMap存同样数量的key至少要500MB以上。极快查询速度O(1)时间复杂度且不依赖磁盘IO或网络延迟。允许误判但绝不漏判它可能把“存在的key”误判为“不存在”假阴性但绝不会把“不存在的key”误判为“存在”假阳性。这对穿透防护至关重要——宁可错杀一千不可放过一个。实操中我们用Guava的BloomFilter实现初始化参数有两个关键点// 预估最大元素数量按业务峰值QPS × 缓存有效期秒估算 long expectedInsertions 1000000L; // 100万 // 期望错误率生产环境建议0.01~0.0011%~0.1% double fpp 0.001; BloomFilterString bloomFilter BloomFilter.create( Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp );这里fppfalse positive probability不是越小越好。错误率从0.01降到0.001内存占用会翻倍。我们实测过在日活500万的APP里用fpp0.001布隆过滤器内存占用1.8MB误判率实测0.09%如果用fpp0.0001内存涨到7MB但误判率只降到0.008%提升有限却多占5MB堆内存——对JVM GC是实打实的压力。所以fpp选0.001是性价比最高的甜点区间。2.3 空值缓存布隆过滤器的兜底搭档布隆过滤器虽好但它有个致命短板它只能判断“是否存在”不能判断“是否曾经存在过”。比如用户刚注册ID10001布隆过滤器里还没有这个key它会放行但用户注销后ID10001又变成无效状态布隆过滤器无法动态删除——它天生不支持删除操作。这就需要第二道防线空值缓存Cache Null。当数据库查不到数据时不返回null而是往缓存里写一个特殊标记如NULL_PLACEHOLDER并设置较短的TTL比如5分钟。这样下次再查同一个key缓存直接命中返回空避免打到DB。关键细节来了空值缓存的TTL必须严格小于业务数据的真实TTL。假设用户信息缓存TTL是30分钟空值缓存TTL就必须设成≤5分钟。为什么因为如果空值缓存TTL太长比如也设30分钟那么用户刚注册完缓存里还是空值就会导致“注册成功却查不到”的线上事故。我们线上统一规定空值缓存TTL 业务缓存TTL / 6且上限5分钟。这个比例来自大量AB测试——既能有效拦截重复穿透请求又保证新数据能在合理时间内可见。提示空值缓存必须用独立的缓存Key避免和真实数据Key冲突。我们约定格式user:10001:NULL而不是直接存user:10001的null值。这样清理缓存时可以精准删除空值不影响真实数据。2.4 实战避坑布隆过滤器的三大隐形陷阱布隆过滤器不是银弹用不好反而埋雷。我踩过的坑现在都成了团队的红线陷阱一初始化容量预估严重不足某次大促前运营同学临时加了个“爆款商品清单”功能开发按日常流量预估布隆过滤器容量为10万。结果大促当天实际涌入200万个新商品ID布隆过滤器误判率从0.1%飙升到15%。后果是大量真实存在的商品被拦截用户刷不出列表。教训布隆过滤器容量必须按“峰值QPS × 最大缓存有效期秒× 1.5安全系数”计算。那个清单功能有效期是24小时峰值QPS 1000正确容量应是1000 × 86400 × 1.5 ≈ 1.3亿。陷阱二未做持久化重启后全失效布隆过滤器在内存里服务重启就清空。我们曾遇到一次发布所有节点重启布隆过滤器变空穿透请求瞬间打爆DB。解决方案用Redis的bitmaps做布隆过滤器的持久化备份。每次向本地布隆过滤器插入key同时用SETBIT bloom:users {hashIndex} 1同步到Redis。重启时从Redis加载bitmap重建本地布隆过滤器。虽然多一次Redis IO但比DB被打挂强百倍。陷阱三未监控误判率盲目信任布隆过滤器的误判率是概率值不是固定值。当实际插入量远超预估时误判率会指数级上升。我们给布隆过滤器加了两个监控指标bloom_filter_actual_size当前实际元素数和bloom_filter_error_rate实时估算误判率。当后者超过0.5%时自动触发告警并降级为空值缓存为主防御。3. 缓存击穿热点Key的定时炸弹3.1 击穿的本质单点失效引发的连锁反应缓存击穿和穿透常被混淆但它们是完全不同的问题。穿透是“查一堆根本不存在的key”击穿是“查一个存在且极其热门的key但这个key刚好过期了”。想象一下某个明星官宣恋情微博热搜第一千万用户同时刷他的主页。这个主页数据缓存在Redis里TTL设为10分钟。就在第10分钟整缓存过期此时恰好有5000个请求同时到达——它们发现缓存miss全部涌向数据库。数据库瞬间被5000个相同SQL打爆响应时间从20ms飙到5s超时熔断整个服务不可用。击穿的可怕之处在于它不是攻击而是自然发生的高并发场景。你无法阻止用户同时刷新也无法预测哪个key会成为热点。它考验的是系统在“单点缓存失效”这一毫秒级窗口内的抗压能力。3.2 互斥锁用分布式锁守住临界区最直接的解法是“加锁”当缓存miss时不是所有请求都去查DB而是让第一个请求去加载其他请求等待。这就是互斥锁Mutex Lock方案。我们用Redis的SET key value NX PX 30000命令实现NX表示key不存在才设置PX表示30秒过期。流程如下请求A发现缓存miss执行SET lock:user:10001 A NX PX 30000成功获取锁请求A查DB写入缓存释放锁DEL lock:user:10001请求B、C…执行同样的SET命令因key已存在返回失败进入等待请求B轮询检查缓存是否写入一旦命中则返回。这里的关键参数是锁的过期时间30000ms。它必须大于DB查询缓存写入的最大耗时。我们线上统一设为DB平均查询耗时 × 3 500ms缓冲。比如DB查用户平均200ms那就设200×35001100ms但我们保守起见设30秒——因为极端情况下DB慢查询可能长达数秒宁可锁久一点也不能让锁提前释放导致多个请求同时加载。注意锁的value必须是唯一标识如请求ID或线程ID不能写死。否则释放锁时可能误删别人持有的锁。正确做法是获取锁后用Lua脚本原子执行“判断value自己的ID再DEL”。3.3 逻辑过期无锁方案的优雅妥协互斥锁简单有效但有副作用所有等待请求都在轮询消耗CPU且响应时间变长。在秒杀等对延迟极度敏感的场景我们改用“逻辑过期”方案。核心思想缓存里存两份数据——业务数据 一个逻辑过期时间戳。比如缓存值是JSON{data: {...}, expireTime: 1712345678900}。应用层读缓存时先检查expireTime是否已过期。如果过期不立即回源而是尝试用SETNX获取一个“后台刷新锁”如果获取成功异步开新线程去DB加载数据更新缓存如果获取失败说明已有线程在刷新当前请求直接返回旧数据哪怕已逻辑过期。这样用户永远能拿到数据只是可能不是最新的而DB压力被均摊到后台线程前端无感知。我们线上对商品详情页就用此方案逻辑过期时间设为真实TTL的1.2倍后台刷新线程池固定5个线程确保不拖垮DB。3.4 永不过期主动更新终极方案的代价最高阶的方案是“缓存永不过期”靠业务逻辑主动更新。比如用户资料页监听用户修改事件MQ或Binlog一旦资料变更立刻删除或更新对应缓存。这样彻底规避了过期问题。但它要求强耦合的事件驱动架构。我们只在核心链路如订单状态、库存用此方案因为事件必须100%可靠投递否则缓存永远不更新更新时机必须精准比如库存扣减后必须等事务提交再发MQ否则出现“缓存已更新DB回滚”的不一致对MQ中间件有强依赖增加了系统复杂度。所以我们的原则是非核心数据用逻辑过期核心数据用永不过期事件驱动普通数据用互斥锁。没有银弹只有权衡。4. 缓存雪崩集体失忆的系统灾难4.1 雪崩的两种形态时间集中型 vs 服务崩溃型缓存雪崩常被误解为“缓存全挂了”其实它有两种更隐蔽、更常见的形态形态一时间集中型雪崩所有缓存Key的TTL都设成一样的比如统一60分钟。到了整点大量Key同时过期请求集中打向DB。这就像一群羊平时散着走突然听到哨声全挤在同一个窄门——门没坏但羊全堵死了。形态二服务崩溃型雪崩Redis集群因网络分区、OOM、配置错误等原因整体不可用。此时所有缓存请求fallback到DBDB瞬间被压垮。这是真正的“雪崩”破坏力最强。两者区别在于时间集中型是“缓存还在但集体失效”服务崩溃型是“缓存没了请求全泄洪”。4.2 TTL随机化最简单有效的预防针对付时间集中型雪崩方案出奇地简单给每个Key的TTL加一个随机偏移量。比如基础TTL是60分钟那就设成60 random(0, 10)分钟即60~70分钟之间随机。为什么有效因为随机化后Key的过期时间呈正态分布。假设你有100万个KeyTTL在60~70分钟间均匀分布那么每分钟过期的Key数≈100万/10分钟10万个而不是整点那一分钟的100万个。DB压力从“瞬间峰值”变成“平滑曲线”。我们线上所有缓存写入都强制走这个逻辑long baseTtl 3600L; // 60分钟 long jitter ThreadLocalRandom.current().nextLong(0, 600); // 0~10分钟随机 redisTemplate.expire(key, baseTtl jitter, TimeUnit.SECONDS);注意jitter不能太大否则业务方难以预估缓存最大存活时间。我们上限设10分钟既保证分散效果又不影响业务SLA。4.3 多级缓存本地缓存是最后的救命稻草当Redis真的挂了怎么办靠DB硬扛肯定不行。这时本地缓存如Caffeine就是你的最后一道防线。多级缓存架构请求 → 本地缓存Caffeine→ Redis → DB。本地缓存的特点是无网络IO读取速度是Redis的10倍容量小通常几百MB但能扛住80%的热点请求支持自动刷新、最大容量淘汰、过期策略。关键配置Caffeine.newBuilder() .maximumSize(10000) // 最多存1万个key .expireAfterWrite(10, TimeUnit.MINUTES) // 写入10分钟后过期 .refreshAfterWrite(5, TimeUnit.MINUTES) // 写入5分钟后异步刷新 .build(key - loadFromRedisOrDb(key));其中refreshAfterWrite是精髓它让本地缓存在过期前自动异步加载新值用户永远拿不到过期数据。即使Redis挂了本地缓存还能继续服务5~10分钟给你抢修留出黄金时间。提示本地缓存必须设比Redis更短的TTL否则Redis恢复后本地缓存里的脏数据会持续污染服务。我们规定本地缓存TTL Redis TTL / 2。4.4 限流降级雪崩发生时的止损开关预防做得再好也可能出意外。所以必须有“雪崩发生时”的应急预案。我们线上有三级熔断一级Redis客户端熔断当Redis响应时间P99 500ms或错误率 5%自动切断Redis调用fallback到本地缓存。二级DB熔断当DB连接池使用率 90%或慢SQL数量 10条/分钟自动拒绝非核心请求如用户中心的非关键字段查询。三级全局降级当核心接口错误率 20%自动开启“降级开关”返回预置的静态兜底数据如商品列表返回最近24小时热销榜。这些开关都通过Apollo配置中心动态控制无需发版5秒内生效。去年双十一Redis集群因机房电力波动短暂抖动就是靠这三级熔断零用户投诉扛过了15分钟故障。5. 缓存一致性CAP理论下的痛苦抉择5.1 一致性难题的根源缓存与DB的天然异步缓存一致性的本质是“如何让缓存里的数据和DB里的数据保持同步”。听起来简单但现实是DB是强一致的缓存是弱一致的DB是事务性的缓存是原子性的DB有ACID缓存只有GET/SET。这两者就像两个不同步的钟表你永远无法让它们分秒不差。我们遇到过最经典的案例电商下单。用户支付成功订单状态从“待支付”变为“已支付”DB事务提交。但缓存里订单状态还是“待支付”用户刷新页面看到“支付失败”客服电话被打爆。问题不在代码而在“更新DB”和“更新缓存”这两个操作无法放在同一个事务里——Redis和MySQL是两个独立系统。5.2 先更新DB再删除缓存Cache Aside最稳妥的默认选择业界公认最稳妥的模式是“Cache Aside”即读先查缓存命中则返回miss则查DB写入缓存再返回写先更新DB成功后再删除缓存不是更新缓存。为什么删缓存而不是更新缓存因为更新缓存有两大风险并发写导致脏数据请求A更新DB后还没来得及更新缓存请求B又更新DB然后更新缓存结果缓存里是B的数据但A的DB更新被覆盖缓存更新失败数据永久不一致更新DB成功但Redis网络超时缓存没更新从此不一致。而删除缓存是幂等操作。即使删失败下次读时会自动回源加载最新数据最多损失一次缓存命中率。但Cache Aside也有缺陷删除缓存后DB更新前如果有读请求会把旧数据重新写入缓存。这就是著名的“删除缓存 DB更新”时间窗口问题。解决方案是“双删”第一次删更新DB前先删缓存让后续读请求不命中更新DB第二次删更新DB后再删一次缓存清除可能在DB更新期间写入的旧数据。我们线上所有写操作都强制双删且第二次删加了500ms延迟用ScheduledExecutorService确保DB事务真正提交后再删。5.3 订阅Binlog最终一致性的高级玩法对于一致性要求极高的场景如金融账户余额我们不用Cache Aside而是用Canal订阅MySQL Binlog解析变更事件异步更新缓存。流程MySQL写入数据生成BinlogCanal Client消费Binlog解析出UPDATE语句根据解析结果精准删除或更新对应缓存Key。优势完全解耦DB变更100%捕获无遗漏。劣势引入Canal中间件运维成本高Binlog解析有延迟通常100ms对MySQL版本和配置有要求必须开启ROW模式。我们只在资金、风控等核心域用此方案并做了三重保障Canal Client集群部署避免单点故障Binlog解析失败时自动告警并转人工补偿缓存更新失败自动重试3次仍失败则写入死信队列由后台任务兜底。5.4 一致性等级没有银弹只有业务分级最后必须认清一个事实100%强一致性在分布式系统里几乎不可能也不必要。我们按业务重要性划分一致性等级强一致资金类余额、流水用Binlog方案容忍100ms延迟最终一致商品信息、用户资料用Cache Aside双删容忍1秒内不一致弱一致推荐列表、热搜榜用定时任务批量更新缓存容忍5分钟不一致。面试时如果被问“如何保证缓存一致性”千万别只答“用XX方案”。要反问“请问这个一致性要求是针对什么业务场景能容忍多长时间的不一致”——这才是资深工程师的思维。6. Spring三级缓存与MyBatis缓存框架级缓存的深度解剖6.1 Spring三级缓存解决循环依赖的精妙设计Spring的三级缓存不是为性能而是为了解决“Bean A依赖Bean BBean B又依赖Bean A”这种循环依赖。它的设计堪称教科书级一级缓存singletonObjects存放完全初始化好的单例BeangetBean()最终从这里取。二级缓存earlySingletonObjects存放“早期暴露的Bean”即实例化完成但尚未注入属性的半成品Bean。三级缓存singletonFactories存放ObjectFactory用于创建早期Bean的工厂函数。关键流程以A依赖BB依赖A为例创建A实例化A → 放入三级缓存ObjectFactory→ 开始注入属性注入B发现B未创建转去创建B创建B实例化B → 放入三级缓存 → 注入属性时发现依赖A此时从三级缓存取A的ObjectFactory调用getObject()得到早期A未注入属性→ 放入二级缓存 → B注入A成功B创建完成放入一级缓存回到A注入B成功A创建完成从二级缓存移除A放入一级缓存。为什么需要三级因为二级缓存存的是Bean实例如果B直接从二级缓存取A而A还在创建中就会拿到不完整的A。三级缓存存的是ObjectFactory可以按需生成且保证每次生成的都是同一实例ObjectFactory内部用new或getBean()保证单例。注意Spring只对单例Bean做三级缓存原型Bean不支持循环依赖——因为原型每次getBean()都新建无法“早期暴露”。6.2 MyBatis二级缓存为什么默认关闭MyBatis的二级缓存namespace级别常被误解为“开箱即用的性能神器”但线上项目我们一律禁用。原因有三第一脏数据风险极高。二级缓存是跨SqlSession共享的。如果两个SqlSession分别操作同一张表一个更新了数据但没刷新缓存另一个读到的就是脏数据。我们曾在线上遇到运营后台修改商品价格缓存未及时失效APP端用户看到的价格还是旧的引发客诉。第二粒度太粗。二级缓存以Mapper namespace为单位所有SQL共用一个缓存。比如UserMapper里既有selectById又有selectAll只要任意一个SQL更新了表整个namespace缓存全清。这导致缓存命中率极低。第三序列化成本高。二级缓存默认用PerpetualCache底层是HashMap但跨SqlSession共享时对象必须序列化。我们实测过一个含10个字段的POJO序列化耗时是直接赋值的8倍GC压力陡增。所以我们的替代方案是用Redis做显式缓存。在Service层对selectById等确定性查询手动redisTemplate.opsForValue().get()更新时手动delete()。虽然代码多几行但可控、可监控、可分级。6.3 Redis缓存设计高并发下的KV存储哲学Redis不是万能胶用错地方比不用更糟。我们总结出三条铁律铁律一只缓存“读多写少”的热点数据用户头像URL、商品分类树、城市编码字典——这些数据变更频率低天/周级查询频率高QPS过万是Redis的最佳拍档。而用户实时聊天记录、订单物流轨迹变更频繁缓存价值低直接查DB更稳。铁律二Key设计必须带业务前缀和版本号user:10001:profile是合格的Key10001是不合格的。前缀明确归属避免Key冲突版本号如v2用于平滑升级。某次我们重构用户模型新增字段直接改缓存结构会导致老代码读错。有了版本号新代码读user:10001:profile:v2老代码读v1互不干扰。铁律三Value必须是原子化的JSON或Protocol Buffers禁止存List、Set等集合类型。因为Redis的HGETALL或LRANGE在网络传输中可能被截断导致客户端解析失败。我们统一用Jackson序列化POJO为JSON字符串或用Protobuf二进制序列化体积小30%解析快2倍。实测Protobuf在千兆网卡下1KB数据序列化网络传输反序列化总耗时1msJSON要1.8ms。最后提醒Redis不是数据库备份。我们严禁把Redis当DB用——不设持久化不依赖RDB/AOF恢复。它的定位永远是“加速层”数据丢了能从DB重建才是健康架构。7. 面试题背后的真相那些没人告诉你的实战经验7.1 “Redis和Memcached的区别”——别再背八股文了面试官问这个真不是想听“Redis支持数据类型多Memcached只支持String”。他想确认你有没有在真实场景做过技术选型。我们选型逻辑很务实用Memcached当需求纯粹是“高速键值缓存”且团队对Redis运维不熟。比如CDN边缘节点缓存要求极致吞吐100万QPS内存够用不需要持久化、不需要复杂数据结构。Memcached的LRU淘汰算法更稳定不会像Redis的LFU在冷热数据切换时抖动。用Redis当需要Pub/Sub做消息广播、用Sorted Set做排行榜、用Lua脚本做原子计数。比如直播间的在线人数统计用Redis的INCRBYEXPIRE比Memcached的CAS重试可靠十倍。所以回答应该是“如果只是缓存HTML片段用Memcached如果要做点赞计数、实时排行榜、分布式锁必须用Redis。”——用场景说话不是用特性列表。7.2 “缓存穿透、击穿、雪崩的区别”——画一张图胜过千言万语这三个词光背定义永远记混。我的记忆法是看攻击对象和影响范围。问题攻击对象影响范围典型场景穿透数据库单个无效Key但高频爬虫扫ID、恶意构造参数击穿数据库单个热点Key瞬时高并发明星主页、秒杀商品详情雪崩整个系统大量Key集体失效或缓存服务宕机Redis集群故障、TTL未随机化画个简笔画穿透是“一根针扎数据库”击穿是“一把锥子扎数据库”雪崩是“一块巨石砸整个系统”。图一画概念就刻脑子里了。7.3 “如何设计一个缓存系统”——面试官在等你的架构思维这个问题没有标准答案但考察点很明确你能否跳出代码从全局视角设计。我们期待的回答结构是定边界明确缓存层级本地/分布式、数据范围哪些表/接口缓存、一致性要求强/最终/弱选工具根据QPS、数据大小、一致性要求选Caffeine/Redis/Memcached定策略TTL怎么设业务SLA决定、淘汰算法LFU/LRU、更新模式Cache Aside/Binlog加防护布隆过滤器防穿透、互斥锁防击穿、多级缓存防雪崩配监控缓存命中率、平均响应时间、错误率、内存使用率——这些指标必须接入PrometheusGrafana。最后一定要补一句“所有设计都要有AB测试验证。比如TTL设60分钟还是120分钟不能拍脑袋要跑一周真实流量看命中率和一致性。”——这才是工程师该有的严谨。7.4 我的血泪教训一次缓存事故的完整复盘去年双十二我们一个促销活动页突然500错误率飙升到15%。排查发现缓存里存了一个巨大的促销规则JSON2MB而Redis单个value默认限制1MB。新规则一写入Redis报错ERR string exceeds maximum allowed size但代码里没捕获这个异常导致后续所有缓存操作都fallback到DBDB被打满。根因有三缓存Value无大小校验写入前没检查JSON长度异常处理不完善Redis异常被吞掉没打日志也没告警缓存Key无分级促销规则和用户信息混在一个Redis集群互相影响。改进措施所有缓存写入前加if (json.length() 800 * 1024) throw new CacheException(Value too large)Redis客户端封装所有异常统一捕获打ERROR日志企业微信告警按业务域拆Redis集群用户域、商品域、促销域物理隔离。这个事故让我明白缓存不是加了就完事它是个需要持续治理的系统。我们后来成立了“缓存治理小组”每月review缓存命中率、异常率、内存碎片率这才是真正的工程能力。8. 结尾缓存不是终点而是性能优化的起点写完这10道题我反而更不敢说“我会缓存了”。因为缓存从来不是孤立的技术点它是连接前端、网关、服务、DB、中间件的神经中枢。你调优一个Redis的maxmemory-policy可能影响到前端的加载速度你改一个MyBatis的缓存配置可能让DBA半夜被电话叫醒。真正的缓存高手眼里没有“缓存”只有“整个请求链路的性能瓶颈”。所以别把这10道题当终点。它们是你打开高并发世界的一把钥匙。接下来你应该去干三件事看监控登录你的APM系统SkyWalking/Prometheus找一个缓存命中率低于70%的接口深挖原因做实验用JMeter模拟1000QPS对比开启/关闭本地缓存的TP99差异把数字记下来画流程图把你负责的最核心接口从用户请求开始一步步画出经过哪些缓存、哪些DB、哪些RPC标出每个环节的耗时和错误率。技术没有捷径但有路径。当你能把缓存从“面试题”变成“每天调试的生产问题”你就真正入门了。至于那些还在背“Redis有五种数据类型”的同学——祝你们早日脱离八股文走进真实的代码世界。