1. 当我们谈论内存泄漏时在说什么

想象你的OpenResty服务是个装满水的浴缸,正常情况下进水和排水应该保持平衡。内存泄漏就像排水口被头发堵住,水位(内存占用)会慢慢上涨直到溢出。最近我就遇到一个线上案例:某个API接口的响应时间每天凌晨三点准时飙升,最终发现是定时任务里的Lua代码在循环中不断创建临时表却忘记释放。

2. 基础监控三板斧

2.1 官方接口直通车

OpenResty自带的ngx.status模块就像浴缸的水位标尺,我们可以通过这个接口实时查看内存状态:

location /memory_status {
    content_by_lua_block {
        local status = ngx.status
        local msg = {
            shared_dicts = status.shared_dicts,  -- 共享字典内存
            worker_vm = status.worker_vm,        -- worker进程内存
            connections = status.connections     -- 连接数
        }
        ngx.say(cjson.encode(msg))
    }
}

调用这个接口会返回类似这样的数据:

{
    "shared_dicts": {"my_cache": 5242880},
    "worker_vm": 134217728,
    "connections": 235
}

技术栈说明:这里使用OpenResty原生的Lua API进行数据采集,配合Nginx的共享内存机制

2.2 第三方监控全家桶

Prometheus方案就像给浴缸安装智能水表,我们可以使用lua-resty-prometheus库:

local prometheus = require("resty.prometheus").init()
local metric_memory = prometheus:gauge(
    "openresty_memory_bytes",
    "Memory usage in bytes",
    {"type"}
)

local function update_metrics()
    local status = ngx.status
    metric_memory:set(status.worker_vm, {"worker_vm"})
    metric_memory:set(status.shared_dicts.my_cache, {"shared_dict"})
end

location /metrics {
    content_by_lua_block {
        update_metrics()
        prometheus:collect()
    }
}

典型应用场景:需要长期趋势分析时,适合在Kubernetes集群中部署的OpenResty服务

2.3 内存快照法

当怀疑某个API导致内存泄漏时,可以使用gdb这个"X光机"生成内存快照:

# 生成core dump文件
gdb -p $(cat /usr/local/openresty/nginx/logs/nginx.pid) -ex "generate-core-file" -ex detach

# 分析内存分配
valgrind --leak-check=full /usr/local/openresty/nginx/sbin/nginx -p /tmp/valgrind-test

注意事项:生产环境慎用,可能引起服务短暂卡顿,建议在压测环境验证

3. 进阶排查手术刀

3.1 共享字典监控术

共享字典就像办公室的公用冰箱,要防止有人把过期食品(内存碎片)堆满:

local function check_shared_dict()
    local dict = ngx.shared.my_cache
    local capacity = dict:capacity()
    local free_space = dict:free_space()
    
    if free_space / capacity < 0.2 then
        ngx.log(ngx.ERR, "共享字典剩余空间不足!当前使用率:", 
                (1 - free_space/capacity)*100, "%")
    end
end

-- 在定时任务中调用
local delay = 60  -- 60秒检查一次
local handler
handler = function()
    check_shared_dict()
    local ok, err = ngx.timer.at(delay, handler)
end

技术优势:精准定位到具体字典的内存问题,适合缓存类应用场景

3.2 LuaJIT内存分析

当怀疑Lua代码有泄漏时,我们可以用lj-analyze这个"显微镜":

# 生成内存分析报告
luajit -p -e 'local t = {} for i=1,1e6 do t[i] = "abc" end' > mem_profile.txt

# 输出示例
ALLOCATIONS BY OBJECT TYPE:
string       45.23%  (12345678 bytes)
table        32.15%  (8765432 bytes)
function     12.01%  (3456789 bytes)

典型问题定位:发现某个路由处理函数中意外创建了大量临时字符串

4. 关联技术深度游

4.1 火焰图定位法

使用systemtap生成内存分配火焰图:

# 安装依赖
yum install systemtap systemtap-runtime

# 采集数据
stap -v -e 'probe process("/usr/local/openresty/nginx/sbin/nginx").function("ngx_palloc") {
    print_stack()
    exit()
}' -c "curl http://localhost/test_api"

输出解读:火焰图最宽的部分就是内存分配的"重灾区"

4.2 内存池管理机制

OpenResty的内存管理就像餐厅的餐盘回收系统:

// 内存池创建(类似领用新餐盘)
ngx_pool_t *pool = ngx_create_pool(4096, cycle->log);

// 内存分配(顾客取用餐具)
void *p = ngx_palloc(pool, 1024);

// 内存池销毁(回收所有餐盘)
ngx_destroy_pool(pool);

常见错误:忘记销毁临时创建的内存池,就像餐厅打烊后还有餐盘散落各处

5. 方案选型指南针

5.1 技术方案对比表

方法 实施难度 准确性 性能损耗 适用阶段
原生接口监控 ★☆☆☆☆ ★★☆☆☆ 日常监控
Prometheus方案 ★★☆☆☆ ★★★☆☆ 长期趋势分析
GDB内存快照 ★★★★☆ ★★★★☆ 严重泄漏排查
共享字典专项监控 ★★☆☆☆ ★★★★☆ 缓存类场景
火焰图分析 ★★★★☆ ★★★★★ 精准定位问题

5.2 避坑指南

  1. 不要在生产环境直接运行valgrind,曾经有工程师因此导致服务中断3小时
  2. 共享字典的碎片率超过30%时,考虑重启worker进程
  3. Lua代码中避免在循环内创建临时表,改用table复用池
  4. 使用lua-resty-lrucache时注意设置合理的缓存淘汰策略

6. 实战案例剖析

某电商大促期间,订单查询接口响应时间从50ms逐渐上升到200ms。通过以下步骤定位问题:

  1. 在Prometheus中发现worker进程内存呈阶梯式增长
rate(openresty_memory_bytes{type="worker_vm"}[5m]) > 1048576  # 每秒增长超过1MB
  1. 使用lj-analyze发现存在大量未回收的userdata对象
USERDATA        18.76%  (6543210 bytes)  -- 可疑的C对象泄漏
  1. 最终定位到某个第三方库没有正确关闭MySQL连接:
local mysql = require "resty.mysql"
local function query_order()
    local db = mysql:new()
    local ok, err = db:connect(config)  -- 这里连接后忘记db:set_keepalive()
    -- ...查询操作...
end  -- db对象没有被正确回收

7. 未来演进风向标

OpenResty 1.21版本新增的ngx.meminfo接口:

local meminfo = ngx.meminfo()
ngx.say("LuaJIT内存使用:", meminfo.jit_total, " bytes")

该接口可细化展示:

  • jit_used:已用内存
  • jit_free:空闲内存
  • gc_objects:GC对象数

8. 总结陈词

经过这次深度探索,我们建立了完整的监控体系:

  1. 日常使用Prometheus做水位监控
  2. 异常时通过火焰图快速定位
  3. 复杂问题用GDB+Valgrind组合拳
  4. 关键业务接口添加内存检查点

就像给OpenResty装上了智能水浸传感器+高清摄像头,让内存泄漏无所遁形。最后送大家一个检查清单:

✅ 新功能上线前跑72小时内存测试 ✅ 共享字典设置使用率告警阈值 ✅ 定期review第三方库的内存管理 ✅ 核心接口添加内存采样日志

记住,内存问题就像牙疼,越早发现治疗成本越低。愿大家的OpenResty服务都能拥有钢铁般的内存管理能力!