追查Lua内存问题的一个过程(一)

发布于 作者 shisongran留下评论

在业务中,尝试使用Lua + Lua的C扩展的方式实现某基础服务。该C扩展中包含了大量的开源组建,提供了数个Lua接口;其中Lua作为胶水来实现外层的业务逻辑,非常典型的架构。在使用这个结构的服务时候,遇到了一些内存问题,下面说下具体的追查思路。

场景

  • 该lua扩展使用在nginx + lua的web架构中,lua扩展是实际上的底层功能实现
  • 该lua扩展在暴露给lua接口的同时,也暴露一份普通的接口出来
  • 业务上线后,发现nginx内存抖动比较大,但总趋势保持增加
    • 在lua代码中,由于实现了下载功能,所以lua虚拟机的内存抖动比较大
    • 实际上发现了偶尔的lua虚拟机crash的情况
  • valgrind跑了lua扩展的普通的接口,尝试复现泄露的情况,多次尝试以失败告终
    • 泄露的数据与实际泄露量相差深远,而实际上的确看到了泄露
    • 猜测:有三方库使用全局buffer,该buffer在进程退出时才会清理
  • 由于三方库代码量巨大,不能一一阅读
  • 内存增长缓慢,约一天时间左右有200MB的泄露,且时间不均匀

问题分析与追查

首先,根据上述现象,我猜测了一些原因:

  1. lua层面的变量没有被置nil,导致lua VM虚拟机内存增长
  2. lua扩展存在内存泄露

第一点比较容易追查,lua提供了一个手工GC的函数,collectgarbage(param)collectgarbage("count")为VM计数 collectgarbage("collect")为手动回收 利用以上函数,我在每次请求开始获取内存状况。发现在lua VM机器执行一段时间后,的确出现了内存异常增加的情况,在手动回收后,内存回复正常。这个问题怀疑是Lua的GC不及时导致lua的内存超出了VM硬限,导致了VM的crashed。

第二点纠结了比较长的时间,目前持续在解决。 由于该lua的扩展本身代码量极大,且拥有大量的第三方库;而使用valgrind的时候,并没有明显的泄露日志(此处怀疑valgrind对内存泄露能力追查有限),及时套上了完整的线上流程,也无法定位到泄露。 为了定位到泄露地点,尝试了将lua扩展中的每个接口分离请求,单个进程只压其中一个接口,以判断内存泄露的地点。使用了以下的代码来判断一次lua扩展调用前后的整体进程内存,分享给大家:

Lua
function getPorcessMemery()
    local pid = ngx.var.pid
    local procFd, errflag = io.open("/proc/" .. pid .. "/status", "r")
    if nil == procFd then
        CLog:warning("open proc file failed, with err[" .. errflag .. "]")
        return false
    end

    local processInfo = procFd:read("*a")
    procFd:close()
    if nil == processInfo then
        CLog:warning("read proc file failed")
        return false
    end
    
    local _, _, _, VmRSS, _ = string.find(processInfo, "VmRSS:(%s+)(%d+)(%s+)kB\n")
    if nil == VmRSS then
        CLog:warning("can't find VmRSS")
        return false
    end
    
    return VmRSS
end

实际上是读取了 /proc/[pid]/status 中的内存状态,当然也可以读取其他状态值。 第二个问题目前是有一些进展,但还没有完全修复,等修复后再更新吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注