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 避坑指南
- 不要在生产环境直接运行valgrind,曾经有工程师因此导致服务中断3小时
- 共享字典的碎片率超过30%时,考虑重启worker进程
- Lua代码中避免在循环内创建临时表,改用table复用池
- 使用
lua-resty-lrucache
时注意设置合理的缓存淘汰策略
6. 实战案例剖析
某电商大促期间,订单查询接口响应时间从50ms逐渐上升到200ms。通过以下步骤定位问题:
- 在Prometheus中发现worker进程内存呈阶梯式增长
rate(openresty_memory_bytes{type="worker_vm"}[5m]) > 1048576 # 每秒增长超过1MB
- 使用
lj-analyze
发现存在大量未回收的userdata对象
USERDATA 18.76% (6543210 bytes) -- 可疑的C对象泄漏
- 最终定位到某个第三方库没有正确关闭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. 总结陈词
经过这次深度探索,我们建立了完整的监控体系:
- 日常使用Prometheus做水位监控
- 异常时通过火焰图快速定位
- 复杂问题用GDB+Valgrind组合拳
- 关键业务接口添加内存检查点
就像给OpenResty装上了智能水浸传感器+高清摄像头,让内存泄漏无所遁形。最后送大家一个检查清单:
✅ 新功能上线前跑72小时内存测试 ✅ 共享字典设置使用率告警阈值 ✅ 定期review第三方库的内存管理 ✅ 核心接口添加内存采样日志
记住,内存问题就像牙疼,越早发现治疗成本越低。愿大家的OpenResty服务都能拥有钢铁般的内存管理能力!