在生产环境中,缓存系统往往面临三种严峻的挑战:穿透、雪崩和热点。
缓存穿透是指查询一个不存在的数据,由于缓存不命中,请求直接打到数据库,导致数据库压力剧增。
空值缓存:对于不存在的 key,在缓存中存放一个空值(或特定标识)并设置较短的过期时间。
当前数据缓存:确保热点查询在缓存中始终有值。
缓存预热:系统上线前,提前将高频访问的数据加载至缓存。
随机失效:在设置过期时间时增加随机扰动,防止大量 key 在同一时刻失效。
缓存雪崩是指大量缓存数据在同一时间集体失效,或者缓存服务宕机,导致瞬间流量全部涌入数据库。
更新锁 (Mutex Lock):采用互斥锁机制,保证同一时间只有一个线程去查询数据库并更新缓存,其余线程等待。
后台更新:由后台异步线程负责缓存的过期检查与刷新,而不由业务请求触发。
某些特定 Key(如大促商品)被极高频率访问,导致单台缓存服务器负载过高。
缓存副本:在多个缓存节点上存储该热点 Key 的副本,分散读取压力。
动态决策:实时监控访问频率,动态识别热点数据并采取扩容或迁移策略。
根据存储内容的不同,分布式缓存主要分为数据缓存和结果缓存。
主要用于存储数据库中的原始记录或对象。
典型场景:实时性要求极高、读多写少的业务。
核心挑战:数据一致性 (Data Consistency)。当数据库更新时,如何保证缓存同步更新?
主要用于存储经过复杂计算或远程调用后的最终结果。
典型场景:计算量大、实时性要求相对不高的业务(如:报表统计、推荐列表)。
核心逻辑:寻找缓存有效期与数据新鲜度之间的平衡。由于结果生成成本高,通常会设置较长的有效期,但需在时效性受损和性能增益之间做出权衡。
分布式缓存的架构设计本质上是在性能、一致性与可用性之间做取舍:
应对稳定性问题(穿透、雪崩、热点)需要从预防(预热、空值缓存)和容错(锁、后台更新)两方面入手。
应对一致性问题,则需要结合业务对实时性的容忍度,选择从“简单过期”到“异步消息补偿”的不同方案。
架构建议: 在设计时,优先考虑“简单易维护”的方案(如空值缓存、异步失效),只有在核心高并发链路上,才引入复杂的分布式锁或事务消息机制。
消息队列异步删除缓存,这是业界最主流的方案。
为什么要“删除”而不是“更新”?
更新缓存可能会导致并发竞争(例如请求 A 和 B 同时更新,由于网络延迟,旧值可能覆盖新值)。
删除缓存操作简单且幂等。
进阶技巧:订阅 Binlog
为了不侵入业务代码,可以使用 Canal 等工具解析 MySQL 的 Binlog,自动向消息队列发送“删除缓存”的指令。这样业务代码只需关注数据库,缓存同步完全自动化。
后台更新,这是防止雪崩的高级手段。
第一层(预防):给缓存过期时间加上随机因子(如$5min + random(1-60s)$),避免缓存集体到期。
第二层(机制):逻辑过期。缓存中不设置真正的 TTL,而是在 Value 里存一个过期时间。当发现逻辑过期时,后台启动异步线程刷新,主线程先返回旧数据。
第三层(兜底):熔断限流。如果缓存真的挂了,利用 Sentinel 或 Hystrix 对数据库请求进行限流,保住数据库不宕机。
核心是有效期与新鲜度的平衡。
策略建议:
热点数据: 较短有效期 + 自动续期。
复杂报表: 固定周期刷新(如每小时凌晨刷新),并在前端展示“数据统计截止时间”,通过 UI 设计降低用户对新鲜度的心理预期。