可直接用 Lua 脚本配合 lua_shared_dict 实现自定义复杂清理:通过前缀索引或时间分片桶在共享内存中显式记录元数据,再原子化批量删除,避免 flush_all 误伤热数据。

直接用 Lua 脚本配合 OpenResty 的 lua_shared_dict 实现“自定义复杂清理”,核心在于:不依赖外部系统、不重启 Nginx、不逐个遍历键,而是通过设计可追踪的元数据结构 + 原子操作,在共享内存内完成精准、可控、低开销的批量清理。
一、为什么不能直接遍历清空?
OpenResty 的共享字典(如 ngx.shared.my_cache)**不提供 keys() 或 scan 接口**。它底层基于红黑树+LRU队列,只支持按 key 精确读写和带过期时间的自动淘汰。强行“清空所有”只能靠 flush_all(),但这是全局暴力清除,会误伤未到期的热数据,且无法按业务维度(如某类商品、某用户前缀、某时间窗口)选择性清理。
二、构建可清理的缓存元数据结构
关键思路:把“哪些 key 属于同一组”这个关系,显式记录在共享内存中。常用两种轻量设计:
-
前缀索引表:为每个业务前缀(如
product:、user:1001:)维护一个 Set 类型的索引条目,每次 set 缓存时同步写入该前缀下的 key 名(例如product:1001写入到idx:product这个列表);清理时先读取idx:product,再对其中每个 key 调用delete()。 -
时间戳+分片桶:对需要按时间清理的缓存(如 5 分钟内的搜索结果),将 key 按分钟哈希到 60 个桶中(如
bucket:202605271905),写入时同时存入主缓存和对应桶;清理旧桶即可批量失效一批数据,无需查 key 列表。
三、用 Lua 脚本实现原子化清理逻辑
以下是一个基于前缀索引的清理函数示例(放在 init_by_lua_block 或独立模块中):
-- 假设已定义:cache = ngx.shared.cache,idx = ngx.shared.idx
function clear_by_prefix(prefix)
local idx_key = "idx:" .. prefix
local keys, err = idx:get(idx_key)
if not keys then return 0 end -- 无索引,跳过
local count = 0
for _, key in ipairs(keys) do
if cache:delete(key) == 1 then count = count + 1 end
end
idx:delete(idx_key) -- 清空索引本身
return count
end
注意:idx 字典建议用 lua_shared_dict idx 2m 单独配置,避免与主缓存争用空间;索引值推荐存为 Lua table 序列化后的字符串(用 cjson.encode),读取时反序列化。
四、触发清理的三种可靠方式
-
HTTP 管理接口:配一个仅限内网访问的
location /admin/cache/clear,用content_by_lua_block解析 query 参数(如?prefix=product),调用上述清理函数并返回统计数。 -
定时主动清理:在
init_worker_by_lua_block中启动一个每分钟检查的 timer,自动清理超过 TTL 的索引桶或过期前缀(适合冷数据归档场景)。 -
写时联动清理:当上游服务通知“商品 1001 已下架”,Nginx 收到 Webhook 后立即执行
clear_by_prefix("product:1001"),保证缓存与业务状态强一致。

















