多级缓存击穿防护核心是“一请求加载、多请求等待”:l1用openresty共享字典存短时效标记,l2用redis分布式锁(set nx ex)控制唯一加载者,加载后原子回填两级缓存(redis长ttl、本地短ttl),并支持降级与空值缓存防穿透。

多级缓存击穿防护的核心逻辑
缓存击穿是指某个热点 key 过期瞬间,大量并发请求同时穿透缓存,直接打到后端服务(如 Tomcat 或数据库),造成瞬时压力激增。OpenResty 的 Lua 脚本可通过“本地缓存 + Redis + 互斥锁”三级协同实现防护,关键不是单点拦截,而是让第一请求加载数据、其余请求等待结果。
用 shared dict 实现本地缓存层(L1)
OpenResty 的 lua_shared_dict 是内存级缓存,响应在 1ms 内,适合存热点 key 的“存在性标记”或短时效数据:
- 在 nginx.conf 的 http 块中定义:
lua_shared_dict l1_cache 64m; - Lua 中读写示例:
local cache = ngx.shared.l1_cache; local val = cache:get("item_1001"); if not val then ... end - 对已知存在的商品 ID,可预设一个轻量标记(如
cache:set("item_1001:exists", true, 5)),避免反复查 Redis
Redis 层加分布式锁(L2 防护)
当 L1 未命中,需访问 Redis;若 Redis 中也为空(key 不存在或已过期),就触发加载逻辑——此时必须防止多个请求同时执行加载:
- 使用
red:set(key, "loading", "NX", "EX", 3)尝试设置一个带过期时间的占位锁(NX 表示仅当 key 不存在时才设) - 若返回成功,说明当前请求获得锁,应立即查询后端并回填 Redis 和 L1 缓存
- 若失败,说明其他请求正在加载,当前请求可短暂休眠(如
ngx.sleep(0.01))后重试读取 Redis,或轮询等待最多 200ms - 注意:锁过期时间要略大于后端加载最大耗时(如设为 3 秒),避免锁提前释放导致重复加载
回种与一致性保障
数据加载完成后,必须同步写入两级缓存,并确保原子性与合理 TTL:
- 先写 Redis(分布式层):
red:set("item_1001", json_data, "EX", 1800)(30 分钟) - 再写本地缓存:
ngx.shared.l1_cache:set("item_1001", json_data, 10)(10 秒,比 Redis 短,避免本地长期脏数据) - 若后端更新通过 Canal 同步 binlog,Lua 层可监听对应消息或接收主动通知,调用
cache:delete()清除两级缓存
兜底与降级处理
任何一级异常都不应阻断请求流:
- Redis 连接失败时,跳过 L2 查询,直接走本地缓存或后端,同时记录 warn 日志
- 本地缓存满时,shared dict 会自动按 LRU 清理,无需手动干预
- 对非核心字段(如商品描述中的富文本),允许缓存空值(
cache:set("item_1001:desc", "", 60)),防止缓存穿透











