别等系统崩溃才想起缓存凌晨两点监控告警疯狂闪烁数据库连接池耗尽所有接口响应时间飙到十秒以上。你翻看代码发现一个高频查询接口居然每次都在直查数据库。这不是段子这是我真实经历过的生产事故。缓存的缺席往往就是系统崩塌的第一块多米诺骨牌。缓存不是锦上添花而是后端开发者的生存刚需。这篇文章不讲教科书式的概念堆砌只讲我从无数次线上故障中提炼出的缓存策略与实战血泪。缓存策略的本质用空间换时间但别换出新的灾难缓存的核心逻辑很简单把热数据存进更快的存储介质通常是内存减少对慢速存储数据库、远程服务的访问。但策略选错轻则缓存命中率低下重则引发雪崩。过期策略TTL 不是设得越长越好最常见的坑是随意给缓存设置一个很长的 TTL比如 24 小时。这会导致“缓存与源数据不一致”的幽灵问题。用户明明改了头像三天后还在显示旧图。正确的做法是按照业务容忍度来设定 TTL。比如用户基本信息可以缓存 5 分钟但库存信息最好不超过 10 秒。如果一定要长缓存请搭配“主动失效”机制更新数据库时同时删除或更新缓存。但要注意删除缓存前先校验数据版本号避免并发写入造成脏数据。淘汰策略LRU 不是银弹当缓存空间满时需要淘汰旧数据。大多数缓存组件默认用 LRU最近最少使用。但 LRU 在面对“突发热点访问”时非常脆弱——一个非热门的 key 被频繁访问几分钟就会把真正的热点挤出去。实战中我会根据数据访问模式混合使用策略对于有明显冷热分层的数据如新闻头条使用 LFU最不经常使用对于批量扫描型数据如定时任务生成的报表使用 FIFO先进先出反而更合适。Redis 的allkeys-lru和volatile-lru要区分好不要对所有 key 一刀切。缓存穿透、击穿、雪崩三个兄弟一个比一个狠这三个名词每个后端面试都会问但很少有人讲清它们的实战解法。穿透查一个不存在的东西客户端疯狂请求一个不存在的数据比如恶意攻击用不存在的 ID缓存查不到就穿透到数据库数据库也查不到流量直接干掉你的 DB。布隆过滤器是分布式场景下的标准答案。在启动时把所有有效 ID 预加载进布隆过滤器或者用 Redis 的 bitmap 实现请求进来先判断 ID 是否存在不存在直接返回失败。注意布隆过滤器有误判率但误判只会让请求打到数据库不会漏掉数据。另一种简单粗暴的办法对空结果也缓存。比如查询用户返回 null就把这个 key 缓存 30 秒。缺点是会缓存大量无效 key所以必须配合给每个 key 加一个固定的 TTL。击穿热点 key 过期瞬间一个超高并发访问的 key 突然过期无数请求同时穿透到数据库瞬间压垮 MySQL。解决方案互斥锁。当缓存 miss 时不是所有线程都去查 DB而是只允许一个线程去查 DB 并重建缓存其他线程等待。实现可以用 Redis 的 SETNX 命令但要注意死锁问题加超时时间或者用 Redisson 的分布式锁。另一个大招热点 key 永不过期逻辑过期。缓存里不设 TTL而是存一个逻辑过期时间字段。后台异步刷新缓存前台命中时判断逻辑时间是否过期过期则返回旧数据的同时触发异步更新。这样永远不会出现缓存空窗期。雪崩大量缓存同时过期比击穿更可怕——大批 key 在同一时刻过期导致大批流量涌向 DB。比如你设置所有缓存 TTL 都是 1 小时整点一到服务器集体“裸奔”。解决过期时间加随机偏移。在基础 TTL 上加一个 [-30s, 30s] 的随机数避免集体过期。同时服务降级也是必备如果数据库压力过大直接返回降级数据比如从静态文件或本地缓存读取。双写一致性缓存与数据库的世纪难题读场景很好解决先读缓存miss 后读 DB 再写缓存。但写场景就复杂了更新数据库后缓存是更新还是删除如果并发高很可能出现“先后写”导致缓存里还是老数据。推荐方案先更新数据库后删除缓存网上很多文章争论先删缓存还是先更新数据库。我推荐先更新数据库后删除缓存因为数据库是最终权威。为什么如果先删缓存再更新数据库在删除缓存和更新数据库之间如果有另一个线程读到缓存 miss 并查询 DB还没更新就会把旧数据写回缓存导致永久不一致。而先更新数据库后再删除缓存如果删除失败下次读缓存会 miss重新从 DB 读回新数据。唯一的代价是删除失败的那一小段时间会有不一致但可以靠延迟双删先删缓存更新 DB等几百毫秒再删一次或其他补偿机制兜底。极高一致性场景读时校验版本号对于金融、库存等要求强一致的数据可以引入版本号。缓存中存一个 versionDB 每更新一次 version1每次读缓存时比较版本不一致则淘汰缓存并重新拉取。虽然复杂但能保证最终一致。实战中的缓存踩坑与优化热点 Key 的发现与处理热点 key 是缓存系统最大的敌人。一个 key 的 QPS 可能达到几十万导致 Redis 实例 CPU 飙升。如何发现事前根据业务经验预估比如秒杀商品 ID、热门新闻 ID事后在 Redis 监控中观察 hotkeysRedis 4.0 以上有hotkeys命令。处理手段本地缓存分布式缓存在应用层使用 Guava Cache 或 Caffeine 做一级缓存减轻 Redis 压力。key 打散比如把热点 key 拆成多个副本如 key:0、key:1……请求进来时随机选择一个副本命中概率不变但每个副本的负载降低。缓存空间膨胀与内存碎片缓存数据越来越多内存不够怎么办不要指望 TTL 自动回收所有数据。我见过一个项目缓存了全量用户信息每个 key 10KB1000万用户就是 100GB远超 Redis 内存。解决方案只缓存高频访问的字段比如用户昵称、头像 URL而不是整个用户对象。用 Protobuf 或 MessagePack 压缩序列化大小。另一点Redis 内存碎片严重时及时执行MEMORY PURGE或重启生产谨慎。更优雅的方式是设置activedefrag yes自动整理。缓存穿透的变种批量查询如果你用mget或批量命令查询多个 key其中某些 key 不存在依然可能引起穿透。处理方案在批量查询时先过滤掉空 key 的集合。或者将批量查询拆成小批量逐个判断是否在布隆过滤器中。分布式缓存的高可用与监控集群模式选型Redis 主流模式有主从、Sentinel、Cluster。对于缓存场景Cluster 是最推荐的因为它天然支持分片和数据自动迁移。但要注意Cluster 不支持多 key 操作如跨 slot 的 mset业务设计时要避免。如果不想引入 Redis 的复杂性可以使用一致性哈希算法在客户端做分片如 Jedis ShardedJedis。但客户端分片缺乏自动故障转移需要配合哨兵或自己做巡检。缓存降级与熔断当缓存服务Redis宕机时不能让系统直接挂掉。设计原则缓存不可用时所有请求直接降级为读 DB但需要加上限流保护。比如用 Hystrix 或 Sentinel 对 DB 访问做熔断避免雪崩。另外缓存最好多级部署本机内存Caffeine 分布式缓存Redis 数据库。当 Redis 挂了自动降级到本地缓存虽然更新慢但至少可用。监控指标必须盯命中率低于 90% 就要排查是否 TTL 太短或热点 key 分散。缓存键数量避免无限制增长设置最大键数报警。内存使用率超过 80% 就要考虑扩容或淘汰。慢查询Redis 的慢查询日志能发现大 key 或复杂操作。最后的心法缓存是系统底线而非万能膏药每次有人问我“系统响应慢加缓存能解决吗”我都会反问缓存只能加速读取不能优化慢查询和烂代码。如果数据库查询本身是select from 全表加缓存只是延迟死亡如果业务逻辑有嵌套 N1 次调用缓存也无法拯救架构。最好的缓存策略是让大多数请求在缓存层就被优雅地终结。你有责任让缓存命中率稳定在 95% 以上让数据库只做它该做的事——持久化与事务。别等到凌晨两点被报警电话惊醒才想起重构缓存策略。现在就去检查你的缓存代码看看 TTL 是不是合理有没有防穿透机制热点 key 是否被保护。你的系统能在缓存失效时优雅降级吗如果不能这就是下一个事故预告单。