一、缓存更新的技术困境

当我们在使用OpenResty构建高性能服务时,缓存是提升性能的利器。但就像超市货架上的生鲜食品一样,缓存数据也有自己的"保质期"。传统被动式缓存更新存在明显的"空窗期"问题:当某个缓存项过期后,第一个请求该数据的用户会触发后端查询,这期间后续请求要么等待要么获取到过期数据。这种设计在高并发场景下可能引发缓存击穿,造成服务雪崩。

二、OpenResty缓存更新技术栈

本文采用OpenResty 1.21.4.1 + LuaJIT 2.1.0-beta3技术栈,配合Redis 6.2.6作为外部存储。其中:

  • lua-resty-redis:用于Redis连接管理
  • lua-resty-lock:实现分布式锁
  • ngx.timer.at:异步任务调度

三、主动更新机制实现方案

3.1 定时器预加载方案

local redis = require "resty.redis"
local red = redis:new()

-- 缓存预热定时器
local function cache_warmer(premature)
    if premature then return end
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "redis连接失败: ", err)
        return
    end
    
    -- 查询即将过期的缓存键(此处为示例逻辑)
    local expiring_keys = red:zrangebyscore("cache_expire_zset", 0, ngx.now()+60)
    
    for _, key in ipairs(expiring_keys) do
        -- 后台异步更新
        ngx.timer.at(0, function()
            update_cache(key)
        end)
    end
    
    red:set_keepalive(10000, 100)
end

-- 每小时执行一次预加载
local ok, err = ngx.timer.every(3600, cache_warmer)
if not ok then
    ngx.log(ngx.ERR, "定时器创建失败: ", err)
end

3.2 惰性更新+后台刷新方案

local shared_cache = ngx.shared.cache_dict
local lock = require "resty.lock"

local function get_cached_data(key)
    local data = shared_cache:get(key)
    
    if data then
        -- 检查剩余有效期
        local ttl = shared_cache:ttl(key)
        if ttl < 60 then  -- 有效期不足1分钟时触发后台更新
            ngx.timer.at(0, function()
                local lock = lock:new("locks", {timeout=2})
                local elapsed, err = lock:lock(key)
                if not elapsed then return end
                
                -- 获取新数据并更新缓存
                local new_data = fetch_from_db(key)
                shared_cache:set(key, new_data, 3600)  -- 重置为1小时
                
                lock:unlock()
            end)
        end
        return data
    end
    
    -- 缓存未命中时的同步更新逻辑
    local new_data = fetch_from_db(key)
    shared_cache:set(key, new_data, 3600)
    return new_data
end

3.3 基于版本号的灰度更新

local function get_with_version(key)
    local current_version = shared_cache:get(key..":version") or 0
    local cached_data = shared_cache:get(key..":"..current_version)
    
    if not cached_data then
        cached_data = fetch_from_db(key)
        local new_version = current_version + 1
        shared_cache:set(key..":"..new_version, cached_data, 86400)
        shared_cache:set(key..":version", new_version)
        return cached_data
    end
    
    -- 后台检查新版本
    if ngx.worker.id() == 0 then  -- 仅worker 0执行版本检查
        ngx.timer.at(0, function()
            local latest_version = get_latest_version(key)
            if latest_version > current_version then
                prefetch_new_version(key, latest_version)
            end
        end)
    end
    
    return cached_data
end

四、关联技术详解

4.1 分布式锁的应用

使用lua-resty-lock模块防止缓存击穿:

local lock = require "resty.lock"
local cache = ngx.shared.cache_dict

local function get_data_safely(key)
    local data = cache:get(key)
    if data then return data end
    
    local locker = lock:new("locks")
    local elapsed, err = locker:lock(key)
    if not elapsed then
        return nil, "获取锁失败"
    end
    
    -- 二次检查防止重复查询
    data = cache:get(key)
    if data then
        locker:unlock()
        return data
    end
    
    -- 实际数据获取逻辑
    data = fetch_from_backend(key)
    cache:set(key, data, 60)
    
    locker:unlock()
    return data
end

4.2 异步任务调度

使用ngx.timer.at实现后台更新:

local function async_update(key)
    local ok, err = ngx.timer.at(0, function()
        local new_data = fetch_from_backend(key)
        ngx.shared.cache_dict:set(key, new_data, 60)
        log_update(key)
    end)
    
    if not ok then
        ngx.log(ngx.ERR, "后台任务创建失败: ", err)
    end
end

五、技术方案对比分析

方案类型 响应延迟 数据一致性 实现复杂度 适用场景
定时器预加载 较高 中等 数据更新频率稳定
惰性+后台刷新 极低 较高 高并发实时系统
版本化灰度更新 中等 最高 金融交易类系统

六、生产环境注意事项

  1. 异常处理机制:所有后台任务必须包裹在pcall中
ngx.timer.at(0, function()
    pcall(function()
        -- 业务逻辑
    end)
end)
  1. 资源限制策略
http {
    lua_max_pending_timers 1024;
    lua_max_running_timers 256;
}
  1. 缓存雪崩防护
local random_ttl = base_ttl + math.random(60)
shared_cache:set(key, value, random_ttl)

七、应用场景分析

  1. 电商价格库存系统:需要实时更新但允许短时延迟
  2. 新闻资讯平台:定时预热+实时更新结合
  3. 金融行情系统:版本化更新确保数据完整性
  4. 社交网络动态:写时失效+异步更新策略

八、技术方案总结

通过三种典型实现方案的对比,我们可以根据业务需求选择最适合的缓存更新策略。对于大多数Web应用,推荐采用"惰性更新+后台刷新"的组合方案,在代码示例2的基础上增加以下增强功能:

local function enhanced_get(key)
    local data = shared_cache:get(key)
    
    -- 热点数据检测
    if data and shared_cache:get_stale(key) then
        local req_count = incr_counter(key)
        if req_count > 100 then  -- 触发主动更新阈值
            ngx.timer.at(0, proactive_update)
        end
    end
    
    -- 降级机制
    if not data then
        return get_stale_data(key) or fetch_from_db(key)
    end
    
    return data
end