一、为什么需要多级缓存?
想象你经营着日均百万PV的电商平台,某次大促活动时发现:
- 用户频繁刷新商品详情页导致数据库QPS突破警戒线
- 高峰时段Redis出现连接池耗尽的情况
- 热门商品的缓存雪崩导致页面加载延迟超过3秒
这正是多级缓存大显身手的时候。我们把缓存比作快递站的分层存放:
- 家门口的快递柜(本地内存缓存)
- 小区驿站(共享内存缓存)
- 区域分拨中心(远程缓存服务)
当95%的请求在前两级缓存就能处理时,系统压力将呈指数级下降。在OpenResty生态中,我们可以通过Lua脚本灵活实现这种分层设计。
二、OpenResty缓存技术栈选择
技术栈说明:
- 核心平台:OpenResty 1.21.4
- 编程语言:Lua 5.1
- 缓存组件:
- lua-resty-lrucache(本地内存)
- ngx.shared.DICT(共享字典)
- lua-resty-redis(远程Redis)
-- 初始化三级缓存结构示例
local function init_caches()
-- 第一级:进程内LRU缓存(容量1000条)
local l1 = lrucache.new(1000)
-- 第二级:共享内存区(需在nginx.conf声明)
-- 声明示例:lua_shared_dict shared_cache 100m;
local l2 = ngx.shared.shared_cache
-- 第三级:Redis集群连接池
local redis = require "resty.redis"
local l3 = redis:new()
l3:set_timeout(1000) -- 1秒超时
local ok, err = l3:connect("redis-cluster", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis连接失败: ", err)
end
return { L1 = l1, L2 = l2, L3 = l3 }
end
三、四级缓存架构实战
3.1 本地内存缓存(L1)
local function get_from_l1(cache, key)
-- 带TTL的智能读取
local item = cache:get(key)
if item and item.expire > ngx.now() then
return item.value
end
cache:delete(key)
return nil
end
local function set_to_l1(cache, key, value, ttl)
-- 存储时记录过期时间戳
cache:set(key, {
value = value,
expire = ngx.now() + (ttl or 60)
})
end
3.2 共享字典缓存(L2)
local function safe_get_l2(shared_dict, key)
local value, flags = shared_dict:get(key)
if value == nil then
-- 防止缓存穿透的互斥锁
local lock_key = key .. ":lock"
if shared_dict:add(lock_key, true, 0.5) then
return nil, "need_miss" -- 触发回源
end
ngx.sleep(0.1)
return safe_get_l2(shared_dict, key)
end
return value
end
3.3 Redis集群缓存(L3)
local function batch_get_redis(redis_conn, keys)
-- 管道批量查询优化
redis_conn:init_pipeline()
for _, key in ipairs(keys) do
redis_conn:get(key)
end
local results = redis_conn:commit_pipeline()
return results or {}
end
3.4 四级降级方案
local function hierarchical_get(key)
local caches = init_caches()
-- L1查询
local val = get_from_l1(caches.L1, key)
if val then return val end
-- L2查询
val, err = safe_get_l2(caches.L2, key)
if val then
set_to_l1(caches.L1, key, val, 5) -- L1缓存短期数据
return val
elseif err == "need_miss" then
-- L3查询
val = caches.L3:get(key)
if val then
caches.L2:set(key, val, 60) -- L2缓存1分钟
set_to_l1(caches.L1, key, val, 5)
return val
end
-- 回源数据库(此处省略具体实现)
local db_val = query_database(key)
if db_val then
-- 异步更新各级缓存
ngx.timer.at(0, function()
caches.L3:set(key, db_val)
caches.L2:set(key, db_val, 300)
set_to_l1(caches.L1, key, db_val, 10)
end)
return db_val
end
end
-- 终极降级方案
return get_emergency_data(key)
end
四、关键技术解析
4.1 缓存穿透防御
-- 共享字典实现的布隆过滤器
local function bloom_filter_check(key)
local bits = ngx.shared.bloom_filter
local hash1 = ngx.crc32_short(key)
local hash2 = ngx.crc32_long(key)
if bits:get(hash1 % 1000000) ~= 1 then
return false
end
if bits:get(hash2 % 1000000) ~= 1 then
return false
end
return true
end
4.2 热点Key发现
-- 基于滑动窗口的热点统计
local HOT_WINDOW = 60 -- 60秒窗口
local function track_hot_key(key)
local counter = ngx.shared.key_counter
local current_time = math.floor(ngx.now())
local window_key = key .. ":" .. (current_time // HOT_WINDOW)
local count = counter:incr(window_key, 1, 0, HOT_WINDOW*2)
if count > 500 then -- 阈值判断
ngx.log(ngx.WARN, "检测到热点Key:", key)
-- 触发本地缓存预热逻辑
end
end
五、应用场景分析
典型使用案例:
- 商品详情页渲染(组合多个服务数据)
- 用户个性化推荐结果缓存
- 秒杀活动的库存缓存
- 地理位置信息缓存
某电商平台实测数据:
- 缓存命中率从78%提升至99.2%
- Redis QPS下降65%
- 平均响应时间从230ms降至89ms
六、方案优缺点对比
优势:
- 响应时间优化明显
- 降低后端存储压力
- 具备分级降级能力
- 缓解缓存雪崩影响
劣势:
- 架构复杂度较高
- 数据一致性维护困难
- 本地缓存更新延迟
- 内存资源消耗增加
七、实施注意事项
- 内存分配策略:
lua_shared_dict shared_cache 200m; # 建议分配总内存的10%
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- 超时策略建议:
- L1缓存:5-15秒
- L2缓存:1-5分钟
- L3缓存:10-30分钟
- 监控指标:
# 查看共享字典状态
nginx -x http_stub_status_module | grep shared_cache
八、总结与展望
通过三级缓存架构,我们成功构建了具备弹性能力的缓存体系。未来的优化方向包括:
- 结合ETCD实现动态配置
- 引入本地磁盘缓存层
- 尝试Caffeine等新型缓存库
建议在实施时做好:
- 缓存Key的版本管理
- 完善监控告警系统
- 定期进行压测验证