一、为什么需要多级缓存?

想象你经营着日均百万PV的电商平台,某次大促活动时发现:

  • 用户频繁刷新商品详情页导致数据库QPS突破警戒线
  • 高峰时段Redis出现连接池耗尽的情况
  • 热门商品的缓存雪崩导致页面加载延迟超过3秒

这正是多级缓存大显身手的时候。我们把缓存比作快递站的分层存放:

  1. 家门口的快递柜(本地内存缓存)
  2. 小区驿站(共享内存缓存)
  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

五、应用场景分析

典型使用案例:

  1. 商品详情页渲染(组合多个服务数据)
  2. 用户个性化推荐结果缓存
  3. 秒杀活动的库存缓存
  4. 地理位置信息缓存

某电商平台实测数据:

  • 缓存命中率从78%提升至99.2%
  • Redis QPS下降65%
  • 平均响应时间从230ms降至89ms

六、方案优缺点对比

优势:

  • 响应时间优化明显
  • 降低后端存储压力
  • 具备分级降级能力
  • 缓解缓存雪崩影响

劣势:

  • 架构复杂度较高
  • 数据一致性维护困难
  • 本地缓存更新延迟
  • 内存资源消耗增加

七、实施注意事项

  1. 内存分配策略:
lua_shared_dict shared_cache 200m;  # 建议分配总内存的10%
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
  1. 超时策略建议:
  • L1缓存:5-15秒
  • L2缓存:1-5分钟
  • L3缓存:10-30分钟
  1. 监控指标:
# 查看共享字典状态
nginx -x http_stub_status_module | grep shared_cache

八、总结与展望

通过三级缓存架构,我们成功构建了具备弹性能力的缓存体系。未来的优化方向包括:

  • 结合ETCD实现动态配置
  • 引入本地磁盘缓存层
  • 尝试Caffeine等新型缓存库

建议在实施时做好:

  1. 缓存Key的版本管理
  2. 完善监控告警系统
  3. 定期进行压测验证