anjia0532 / lua-resty-maxminddb

A Lua library for reading MaxMind's Geolocation database
Apache License 2.0
97 stars 31 forks source link

查询时内存不断增长--内存泄露 #6

Closed spyth closed 6 years ago

spyth commented 6 years ago

Hi,不知道是不是调用的方式不对,查询的时候内存会一直不断增长。

下面是我写的脚本,可以重现。 环境是:CentOS 7.5.1804, openresty/1.13.6.2

-- test.lua
local geoip = require("resty.maxminddb").new("/usr/local/share/GeoIP/GeoLite2-Country.mmdb")
for i = 1, 200000 do
  local ip = string.format("%s.%s.%s.%s", math.random(1, 255), math.random(1, 255), math.random(1, 255), math.random(1, 255))
  local res, err = geoip:lookup(ip)
  if res and res.country and res.country.iso_code then
    print(i, res.country.iso_code)
  end
end

$ resty test.lua

anjia0532 commented 6 years ago

如果内存匀速增长的话,可能是内存泄露了,你可以通过火焰图来看看。

anjia0532 commented 6 years ago

ping @spyth ,你是通过resty-cli来测试的?有没有尝试用最新的openresty+ab/wrk/jmeter等进行测试?

spyth commented 6 years ago

pong @anjia0532 ; 一开始是在生产环境的 openresty 上发现的,上线了几个钟服务器就内存爆了重启。openresty 是最新版本。我没有用 ab 去测试,不过有简单写了个脚本发模拟请求就复现了。 上面的脚本我之前有生成了个火焰图 perf.svg.zip,不知道生成的对不对。

anjia0532 commented 6 years ago

真是不太好意思,最近一段时间比较忙,并且后来我们换用了ipip.net的ip库,所以就不怎么用maxminddb了。不过看你的火焰图不太明显,最好是新起一个openresty(排除其他因素的影响),只通过ab/wrk压测ip(ip最好是随机ip,防止ip缓存),并记录火焰图,可以看出瓶颈在哪。

同时,根据你的描述,应该是内存泄露.可以通过春哥开源的 https://github.com/openresty/stapxx#sample-bt-leakshttps://github.com/openresty/openresty-systemtap-toolkit#ngx-leaked-pools 进行调试。

或者如果你不是很着急的话也可以等我稍后有空的时候再跟踪此问题。

znikang commented 6 years ago

MMDB_get_entry_data_list . 沒有 release

entry_data_list

extern void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list);
補進流程上 就會清 memory 了

anjia0532 commented 6 years ago

@znikang 感谢指点,开始我也以为是未进行垃圾回收导致的(我是java程序员。。。) 但是加了回收后(参考 https://github.com/maxmind/libmaxminddb/blob/master/doc/libmaxminddb.md#mmdb_free_entry_data_list ),依然存在内存泄露问题,后来根据火焰图分析,发现核心问题是不断初始化导致的问题。不是指针未回收。据此将对象重用即可解决。通过

cat server.conf
server {
    listen 80;
    server_name 127.0.0.1;
    location / {
        content_by_lua_block{
            local cjson = require 'cjson'
            local geo = require 'resty.maxminddb'
            if not geo.initted() then
                geo.init("/path/to/GeoLite2-City.mmdb")
            end
            local res,err = geo.lookup(ngx.var.arg_ip or ngx.var.remote_addr) --support ipv6 e.g. 2001:4860:0:1001::3004:ef68

            if not res then
                ngx.log(ngx.ERR,'failed to lookup by ip ,reason:',err)
            end
            ngx.say("full :",cjson.encode(res))
            if ngx.var.arg_node then
               ngx.say("node name:",ngx.var.arg_node," ,value:", cjson.encode(res[ngx.var.arg_node] or {}))
            end
        }
    }
}
cat test.lua
wrk.method = "GET";
wrk.body = "";

logfile = io.open("wrk.log", "w");

request = function()
ip = tostring(math.random(1, 255)).."."..tostring(math.random(1, 255)).."."..tostring(math.random(1, 255)).."."..tostring(math.random(1, 255))
path = "/?ip=" .. ip
return wrk.format(nil, path)
end

response = function(status,header,body)
logfile:write("\nbody:" .. body .. "\n-----------------");
end
wrk -t10 -c10 -d60s -s ./test.lua --latency http://127.0.0.1

docker run --rm  -v `pwd`:/data williamyeh/wrk -s test.lua --latency  http://127.0.0.1

内存一直保持在0.x%-1%左右

附件是0.4和0.5的火焰图对比。

maxmind_flame.zip

@spyth 你可以用0.5的测一下

znikang commented 6 years ago

呵呵 把它放在 lua init 搞定了

anjia0532 commented 6 years ago

@znikang 恩,放在 init_by_lua*也可以,是我在readme.md里写的demo误导了大家。 用

            local geo = require 'resty.maxminddb'
            if not geo.initted() then
                geo.init("/path/to/GeoLite2-City.mmdb")
            end

适用性更强一些,既可以放到init 初始化 ,也可以在access_by_lua,log_by_lua,content_by_lua处直接使用。