Erlang 二进制泄漏?
Posted
技术标签:
【中文标题】Erlang 二进制泄漏?【英文标题】:Erlang Binary Leak? 【发布时间】:2018-02-22 07:54:30 【问题描述】:我们有一个处理大型 json 有效负载的 erlang/elixir 应用程序(在 18/erts 7.3.1 上)。
这是一个典型的工作流程:
监听器从 rabbitmq 获取令牌并发送到 gen_server。
gen_server 将令牌放入具有未来时间(当前 + n 秒)的 ETS 表中。 gen_server 中的计划作业将从 ETS 中提取过期令牌并使用这些令牌启动几个短期进程。
这些短暂的进程从 elasticsearch 下载 30-50k json 有效负载(使用 hackney)并对其进行处理,然后将结果上传回 elasticsearch,然后进程立即终止。我们跟踪这些过程并确认它们已经死亡。我们每秒处理 5-10 个此类请求。
问题:我们看到一个不断增长的二进制空间,并在 48 小时内增长到几个 GB(通过观察者和调试打印看到)。手动 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 等,这些应用程序也没有占用内存。
过去有没有人遇到过类似的问题?希望有任何解决方案。
2017 年 9 月 15 日更新:
我们将我们的应用程序更新到 Erlang 19/ERTS 8.3,并将 hackney 和毒库更新到最新版本,但仍然没有任何进展。这是 GenServer 中的一些日志,它使用 spawn/receive 或 send_after 定期向自身发送消息。在每个 handle_info 中,它查找一个 ets 表,如果找到任何“合格”条目,它就会产生新进程。如果不是,它只返回一个 :noreply, state。我们在函数入口处打印 VM 二进制空间信息(以 KB 为单位),日志如下所示。这是一天中的“安静”时间。可以看到二进制空间逐渐增大。 :recon.bin_leak(N) 或 :erlang.garbage_collect() 再次对这种增长没有影响。
11:40:19.896 [警告] 二进制 1: 3544.1328125
11:40:24.897 [警告] 二进制 1: 3541.9609375
11:40:29.901 [警告] 二进制 1: 3541.9765625
11:40:34.903 [警告] 二进制 1: 3546.2109375
---一些处理---
12:00:47.307 [警告] 二进制 1: 7517.515625
---一些处理---
12:20:38.033 [警告] 二进制 1: 15002.1328125
在我们的旧 Scala/Akka 应用程序中,我们从未遇到过这样的情况,该应用程序处理了 30 倍以上的容量运行多年而没有出现问题或重新启动。我编写了这两个应用程序。
【问题讨论】:
如果 gc 和 recon 都没有释放内存,这不是通常所说的“二进制泄漏”,似乎还有其他事情发生。您是否保留收到的有效负载的任何部分?如果您将子二进制文件存储到负载中的某个地方(可能是日志或其他报告系统),可能会发生类似的情况。 感谢您的澄清。我们使用较大负载(Json -> Dict)中的值来构造不同的负载,然后丢弃原始负载。我们将生成的有效负载发送到 elasticsearch。处理此问题的整个过程终止。我们确实将兔子事件持久化到数据库,但它非常小。 你不会在某个时候将令牌转换为原子吗?它可能是分配给原子的内存,它永远不会释放回操作系统。此外,在无限期运行时,VM 会崩溃,但您还无法达到这一点。 您可以在解析 JSON 后尝试此操作,转换每个二进制文件由binary:copy/1
复制的结构。您当然会看到性能下降,但请观察泄漏是否仍然存在。如果它消失了,您就知道问题是保留原始 JSON 的子二进制文件,您可以进一步调查。这是对您的假设的一个很好的检验。
The Json --> 由 Poison 处理的字典,我猜它使用的是 Jiffy。关键是,检索二进制文件并进行所有转换的整个过程都死了。我明白你在说什么,但我不明白复制字典中的每个键和值会有什么不同,对吗?我刚刚用更多信息更新了这个问题。
【参考方案1】:
我们发现 memory_leak 来自一个可重用的私有库,该库将消息发送到 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]) 我可以省去一些麻烦。
感谢所有输入和 cmets。非常感谢!
【讨论】:
以上是关于Erlang 二进制泄漏?的主要内容,如果未能解决你的问题,请参考以下文章