如何确定基于 Apache/PHP 的 Web 应用程序中明显内存泄漏的原因?

ond*_*rej 18 php memory apache-2.2

大约每周一次,但有时甚至一天几次在正常运行几天后,我的 EC2 实例变得无响应。Munin 的内存图讲述了一个非常简单的故事:分配给“应用程序”的内存开始增长并且不会停止,直到交换完全使用并且实例有效地降低到膝盖为止。另一个自定义图显示不断增长的进程​​是apache2。

我使用 mod_php 和一些 PHP 脚本运行标准的 prefork Apache 设置。正如您在下图中所看到的,发生一些事情会触发 apache2 进程开始消耗越来越多的内存。我及时发现了第一个绿色尖峰,并在事情失控之前重新启动了 Apache。第二个峰值变得更远了,实例必须彻底重启。

穆宁记忆图

我想知道的是如何最好地调试它。除了使用 FastCGI 设置 PHP 并让它在自己的进程中运行之外,有什么好方法可以找出是 Apache 还是 PHP 和我的代码的组合导致内存使用过多?你们会采取什么步骤来追踪这个问题?


更新:正如马特在下面建议的那样,在涉及 strace 后,我能够追踪泄漏。

在找到内存中逐渐且持续增长的 apache2 进程后,我向我的 PHP 脚本添加了更多的 error_log() 调用,以打印出在其执行过程中各个点使用的 RSS 总量(使用 ps 的输出)。然而,结果证明这是一种误导——虽然 RSS 似乎只有在我的脚本执行完毕后才会跳转,但后来的调试表明情况并非如此。当心!

幸运的是,所有这些 error_log() 调用最终都证明是有用的。当我启动 strace ( strace -p <pid> -tt -o trace.log -s 256) 时,我看到对于每个请求,进程分配了大约 400k 的内存(查找 'brk' 系统调用并从最后一个调用的参数中减去第一个调用的参数——一些通常是一个之后)。然后,我搜索了包含我的 error_log() 消息的最新“写入”系统调用,它告诉我在脚本中的哪个点分配了内存。通过一些更具策略性的 error_log() 调用来更准确地查明位置,我终于找到了罪魁祸首。

当我们从 PHP 脚本调用 curl_exec() 时,内存正在泄漏。一些与处理 SSL 连接相关的 curl 代码做错了——当我切换到 HTTP 时,泄漏就消失了。Curl 的更新日志引用了一些在 7.19.5 中修复的 SSL 内存泄漏(我们在 7.18.2 上),所以我接下来会尝试。

与此同时,我运行的 MaxRequestsPerChild 非常低,这使 Apache 保持在合理的范围内。谢谢大家!

小智 6

追踪导致问题的原因可能会让人头疼。如果我遇到这样的问题,我会做的第一件事就是减少MaxRequestsPerChild到一个非常低的数字(~100-200),看看这是否有所作为。如果是这样,那么您的代码可能会在某个地方的循环中泄漏内存,并且您需要运行代码审计。

另一件要查看的事情是 Apache 的 fullstatus,看看您是否可以找出导致内存泄漏的特定请求。获取可疑进程的 PID,并对它们运行 strace。