一、为什么需要监控Lua执行时间?

当你的Web应用突然响应变慢时,就像快递仓库突然堆积了上千个包裹,每个分拣工(Nginx worker)都在拼命工作却效率低下。在OpenResty架构中,Lua脚本就是这些分拣工的操作手册,它们的执行效率直接决定了整个系统的吞吐能力。

最近我遇到一个真实案例:某电商平台的商品详情页接口在促销期间频繁超时。通过监控发现,某个Lua脚本在流量高峰时平均执行时间从5ms暴增到200ms,最终定位到是未优化的JSON序列化代码导致。这充分说明执行时间监控的重要性。

二、监控原理与技术栈选择

2.1 OpenResty计时核心机制

OpenResty基于LuaJIT的时间函数提供了两种精度:

-- 毫秒级精度(适合常规监控)
local start_time = ngx.now() * 1000  -- 转换为毫秒

-- 微秒级精度(需要高精度场景)
local start_time = ngx.now() * 1000000  -- 转换为微秒

2.2 推荐技术栈组合

本文采用以下技术方案:

  • OpenResty 1.21.4.1
  • LuaJIT 2.1.0-beta3
  • lua-resty-core 0.1.24
  • lua-resty-lrucache 0.13

三、四种实战监控方案

3.1 基础版:手动埋点计时

location /api/product {
    access_by_lua_block {
        local start = ngx.now()
        
        -- 业务逻辑代码
        local res = query_database("SELECT * FROM products")
        process_product_data(res)
        
        local cost = (ngx.now() - start) * 1000  -- 转换为毫秒
        ngx.log(ngx.INFO, "[PERF] product_api cost ", cost, "ms")
    }
}

特点:适合快速验证,但需要侵入代码

3.2 增强版:共享字典统计

http {
    lua_shared_dict perf_stats 10m;  -- 声明共享内存区域
}
location = /report {
    content_by_lua_block {
        local stats = ngx.shared.perf_stats
        local sum = stats:get("product_api_sum") or 0
        local count = stats:get("product_api_count") or 0
        
        ngx.say("平均耗时:", sum/count, "ms")
    }
}

location /api {
    access_by_lua_block {
        local start = ngx.now()
        
        -- 执行业务逻辑
        handle_api_request()
        
        local cost = (ngx.now() - start) * 1000
        local stats = ngx.shared.perf_stats
        stats:incr("product_api_sum", cost)
        stats:incr("product_api_count", 1)
    }
}

优势:数据聚合更便捷,适合长期监控

3.3 高阶版:请求全链路追踪

server {
    log_by_lua_block {
        local ctx = ngx.ctx
        ngx.log(ngx.INFO, 
            "[TRACE] total=", ctx.total_cost or 0,
            " db=", ctx.db_cost or 0,
            " cache=", ctx.cache_cost or 0)
    }

    location /api {
        access_by_lua_block {
            ngx.ctx.total_start = ngx.now()
            
            -- 数据库操作
            local db_start = ngx.now()
            query_database()
            ngx.ctx.db_cost = (ngx.now() - db_start) * 1000
            
            -- 缓存操作
            local cache_start = ngx.now()
            query_cache()
            ngx.ctx.cache_cost = (ngx.now() - cache_start) * 1000
            
            ngx.ctx.total_cost = (ngx.now() - ngx.ctx.total_start) * 1000
        }
    }
}

亮点:支持多阶段耗时分析,精准定位瓶颈

3.4 终极方案:动态采样监控

local function should_sample()
    -- 按1%概率采样
    return math.random(100) == 1
end

location /api {
    access_by_lua_block {
        local sample = should_sample()
        local start = sample and ngx.now() or nil
        
        process_request()
        
        if sample then
            local cost = (ngx.now() - start) * 1000
            ngx.log(ngx.INFO, "[SAMPLING] cost=", cost)
        end
    }
}

适用场景:高并发下的性能监控,有效降低系统开销

四、技术方案对比分析

方案 精度 性能损耗 实施难度 数据维度
手动埋点 单一
共享字典 聚合
全链路追踪 多维
动态采样 可变 最低 样本

五、五大核心注意事项

  1. 时间精度陷阱
-- 错误示例(浮点数精度丢失)
local t1 = ngx.now()
local t2 = ngx.now()
assert(t2 > t1)  -- 可能在极短时间内失败

-- 正确做法(使用整数存储)
local t1 = ngx.now() * 1000
local t2 = ngx.now() * 1000
  1. 日志级别风暴
# nginx.conf 调整日志级别
error_log logs/error.log info;  -- 生产环境建议warn
  1. 共享字典的原子操作
local stats = ngx.shared.perf_stats
stats:incr("total_cost", cost)  -- 原子操作安全
-- stats:set("total", stats:get("total") + cost) -- 非原子,存在竞态风险
  1. 第三方库的版本适配
# 安装指定版本的lua-resty-core
opm install openresty/lua-resty-core@0.1.24
  1. 监控指标的维度设计
-- 好的指标示例
local metric_name = "api_"..ngx.var.host.."_"..ngx.var.uri

-- 差的指标示例
local metric_name = "api_cost"  -- 缺乏维度信息

六、典型应用场景解析

6.1 突发流量下的自动熔断

local circuit_breaker = {
    last_failure_time = 0,
    failure_count = 0
}

access_by_lua_block {
    local start = ngx.now()
    
    -- 业务逻辑
    handle_request()
    
    local cost = (ngx.now() - start) * 1000
    if cost > 1000 then  -- 超过1秒视为异常
        circuit_breaker.failure_count = circuit_breaker.failure_count + 1
        if circuit_breaker.failure_count > 10 then
            ngx.exit(503)  -- 触发熔断
        end
    end
}

6.2 灰度发布验证

location /api {
    access_by_lua_block {
        local start = ngx.now()
        
        -- 新版本代码
        new_feature()
        
        local cost = (ngx.now() - start) * 1000
        report_to_monitor("v2", cost)  -- 单独统计新版本耗时
    }
}

6.3 慢查询自动分析

local slow_log = ngx.config.subsystem == "http" and 
    require "ngx.errlog" .get_subsystem("http")

location /api {
    content_by_lua_block {
        local query_start = ngx.now()
        run_sql_query()
        local cost = (ngx.now() - query_start) * 1000
        
        if cost > 500 then
            ngx.log(ngx.WARN, "SLOW_QUERY:", ngx.var.request_uri)
            slow_log.record("sql_slow", cost)  -- 记录到专用日志
        end
    }
}

七、总结与展望

通过本文介绍的四种监控方案,我们可以根据实际场景灵活组合使用。未来趋势方面,OpenResty社区正在推动将更多的监控指标集成到ngx_metric模块中,预计2024年发布的OpenResty 2.0可能会内置Prometheus格式的指标导出功能。

建议在生产环境中采用分层监控策略:

  1. 全量采集基础耗时指标
  2. 按1%采样率收集详细跟踪数据
  3. 异常耗时自动触发线程快照
  4. 结合OpenTracing实现分布式追踪