不能只用Redis做一级缓存,因其存在网络延迟(内网RTT达0.5–2ms)和单点依赖两大硬伤,QPS过万时易成瓶颈并引发数据库雪崩;二级缓存通过将最热5%数据钉在进程内存中,绕过网络跳转提升性能。

直接用 lru-cache + redis 组合,但必须控制好本地缓存粒度和失效时机,否则多实例下数据不一致、内存爆掉、缓存雪崩都会立刻出现。
为什么不能只用 Redis 做一级缓存
单靠 Redis 无法解决两个硬伤:一是网络延迟(哪怕内网 RTT 也常达 0.5–2ms),在 QPS 过万时会成为瓶颈;二是 Redis 成为单点依赖,一旦抖动或连接池耗尽,所有请求直打 MongoDB,瞬间压垮数据库。二级缓存的本质不是“多加一层”,而是把最热的那 5% 数据(比如首页商品列表、实时排名)稳稳钉在进程内存里,绕过网络跳转。
lru-cache 的 max 和 ttl 怎么设才不翻车
这两个参数不是拍脑袋定的,得按真实热点分布来调:
-
max要小于 Node.js 进程常驻内存的 15% —— 比如你给服务分配了 2GB 内存,max: 200(项数)比max: 2000更安全,因为每条缓存可能含嵌套文档,实际占用远超预期 -
ttl必须短于业务容忍的数据陈旧时间,且要错开 —— 比如商品价格缓存设ttl: 30000(30 秒),但排行榜缓存设ttl: 8000(8 秒),避免大量 key 同时过期引发回源洪峰 - 别信“默认 1000 项很安全”——用
memoryCache.info()定期打印size和length,发现length长期 >max * 0.8就得缩容或拆 key
本地缓存和 Redis 如何协同更新
不能等 Redis 过期才刷新本地缓存,必须主动同步:
- 写操作走“先删 Redis,再删本地缓存” —— 用
redis.del('product:123')+memoryCache.delete('product:123'),而不是更新,避免竞态 - 读操作严格按顺序:先查
memoryCache.get()→ 命中则返回;未命中再查redis.get()→ 命中则memoryCache.set()并返回;都未命中才查 MongoDB,然后两级写入 - 对极热 key(如
rank:hourly),加一个后台setInterval主动预热,但必须带随机偏移(Math.random() * 5000),防止所有实例同时刷
多实例部署时本地缓存一致性怎么破
这是最容易被忽略的致命点:4 个 PM2 实例各自维护一份 lru-cache,一个实例删了本地缓存,其他三个还拿着脏数据。
解法只有两个,且必须二选一:
- 用 Redis Pub/Sub 广播失效事件 —— 每个实例订阅
cache:invalidate频道,收到{ key: 'product:123' }就执行memoryCache.delete(key) - 彻底放弃强一致,改用“软过期”策略:本地缓存
ttl设为 10 秒,但每次读取时额外检查 Redis 中该 key 的updated_at时间戳,若 Redis 版本更新,则立即 reload 并重置本地 TTL
没有中间路线。选 Pub/Sub 就得多维护一个订阅逻辑;选软过期就得接受最多 10 秒的不一致窗口 —— 但比全量脏数据强得多。


















