Erlang二进制泄漏?

5
我们有一个 erlang/elixir 应用程序(基于18/erts 7.3.1),它会处理大量的 json 负载。以下是典型的工作流程:
1. 监听器从 rabbitmq 获取令牌并将其发送到 gen_server。 2. gen_server 将令牌插入 ETS 表中并设置过期时间(当前时间 + n 秒)。gen_server 中的定时任务会从 ETS 中获取过期的令牌,并使用这些令牌启动多个短暂的进程。 3. 这些短暂的进程使用 hackney 从 elasticsearch 下载30-50k的 json 负载并进行处理,然后将结果上传回 elasticsearch,最后立即终止进程。我们每秒处理5-10个此类请求。
问题:我们发现二进制空间不断增长,在48小时内增长到几个 GB(通过 observer 和 debug prints 观察到)。手动进行GC也没有影响。
我们已经添加了“recon”并运行了 recon:bin_leak,但这只释放了一些 KB 并且对持续增长的二进制空间没有影响。
堆栈信息:Erlang 18/erts 7.3.1, elixir 1.3.4, hackney 1.4.4, poison 2.2.0, timex 3.1.13等,这些应用都没有占用内存。
是否有人遇到过类似的问题?我们会非常感谢任何解决方案。
更新 9/15/2017:
我们将应用程序更新到 Erlang 19/ERTS 8.3,并将 hackney 和 poison 库更新到最新版本,但仍然没有进展。以下是一个 GenServer 中的日志,该 GenServer 定期向自己发送消息,使用 spawn/receive 或 send_after。每次 handle_info 调用时,它都会查找 ETS 表,如果找到任何“符合条件”的条目,则会生成新的进程。否则,它只会返回 {:noreply, state}。我们在函数入口处打印VM的二进制空间信息(以KB为单位),下面是日志列表。这是一天中比较“安静”的时间。您可以看到二进制空间的逐渐增加。再次强调::recon.bin_leak(N) 或 :erlang.garbage_collect() 对这种增长没有影响。
11:40:19.896 [warn] binary 1: 3544.1328125
11:40:24.897 [warn] binary 1: 3541.9609375
11:40:29.901 [warn] binary 1: 3541.9765625
11:40:34.903 [warn] binary 1: 3546.2109375
--- 一些处理 ---
12:00:47.307 [warn] binary 1: 7517.515625

---一些处理---

12:20:38.033 [警告] 二进制1:15002.1328125

我们在旧的Scala / Akka应用程序中从未遇到过这样的情况,该应用程序处理的数量是当前应用程序的30倍。旧应用程序在多年运行后也没有出现任何问题或重启。我编写了这两个应用程序。


1
如果既不是垃圾回收(gc)也不是释放(recon)内存,这通常不被称为“二进制泄漏”,似乎还有其他情况。您是否持久化接收到的有效载荷的任何部分?如果将子二进制文件存储在有效载荷的某个位置-例如日志或其他报告系统-可能会发生类似的情况。 - michalmuskala
感谢澄清。我们使用较大的有效负载(Json-> Dict)中的值来构建不同的有效负载,然后丢弃原始有效负载。我们将生成的有效负载发送到elasticsearch。处理此过程的整个过程终止。我们确实将兔子事件持久化到数据库中,但它非常小。 - adean
3
在解析JSON后,您可以尝试使用binary:copy/1复制每个二进制,并转换结构。当然,这会导致性能下降,但请观察漏洞是否仍存在。如果它消失了,那么您就知道问题是保留子二进制到原始JSON中,您可以进一步调查。这是验证您假设的好方法。 - Hynek -Pichi- Vychodil
@adean 他的意思是因为任何超过一定大小(我忘记确切大小,但不是很大,大约64字节左右)的二进制在进程之间是引用而不是复制。如果ProcA接收到一个二进制并从中提取一部分发送给ProcB,然后ProcA死亡,整个二进制仍然在内存中,只有那个段被引用,实际上被ProcB使用。这就是我们拥有binary:copy/1函数的原因。 - zxq9
你是从gen_server中执行了":erlang.garbage_collect()"还是发出了":erlang.garbage_collect(Pid)"?与JVM不同,garbage_collect()只会收集你从中运行它的进程,因此如果你在iex中执行它,它将无法帮助你调试。 - Joel Borggrén-Franck
显示剩余5条评论
1个回答

1
我们发现内存泄漏来自一个私有的可重用库,该库向Graylog发送消息,并在通过gen_udp发送之前使用以下函数压缩数据。
defp compress(data) do
    zip = :zlib.open()
    :zlib.deflateInit(zip)
    output = :zlib.deflate(zip, data, :finish)
    :zlib.deflateEnd(zip)
    :zlib.close(zip)  #<--- was missing, hence the slow memory leak.
    output
end

使用term_to_binary(data, [:compressed])代替可以避免一些麻烦。

感谢所有的输入和评论。非常感激!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接