大约每周一次,但有时甚至每天几次,在运行了几天后,我的 EC2 实例变得没有响应。Munin 的内存图表讲述了一个非常简单的故事:分配给“应用程序”的内存开始增长,直到交换空间被完全使用并且实例实际上被压垮才会停止。另一个自定义图表显示,不断增长的进程是 apache2。
我运行一个带有 mod_php 和一些 PHP 脚本的标准 prefork Apache 设置。如下图所示,发生了一些事情,触发了 apache2 进程开始消耗越来越多的内存。我及时捕捉到了第一个绿色峰值,并在事情失控之前重新启动了 Apache。第二个峰值更猛烈一些,实例必须立即重新启动。
我想知道的是如何最好地调试这个问题。除了使用 FastCGI 设置 PHP 并使其在自己的进程中运行之外,有什么好方法可以找出是 Apache 还是 PHP 和我的代码的组合导致了内存使用量过大?你们会采取什么步骤来追踪这个问题?
更新:我能够在使用 strace 之后追踪到泄漏,正如 Matt 在下面所建议的那样。
在发现内存中逐渐且持续增长的 apache2 进程后,我向 PHP 脚本添加了几个 error_log() 调用,打印出执行过程中各个点使用的 RSS 总量(使用 ps 的输出)。然而,这被证明是误导性的——虽然 RSS 似乎只在我的脚本执行完成后才开始增长,但后来的调试表明事实并非如此。要小心!
幸运的是,所有这些 error_log() 调用最终都派上了用场。当我启动 strace ( strace -p <pid> -tt -o trace.log -s 256
) 时,我看到对于每个请求,该进程分配了大约 400k 的内存(查找“brk”系统调用并从最后一个调用的参数中减去第一个调用的参数——通常几个调用会接连出现)。然后,我搜索了包含我的 error_log() 消息的最近的“write”系统调用,该消息告诉我在脚本中的哪个位置分配了内存。通过更有策略地放置一些 error_log() 调用来更准确地查明位置,我终于找到了罪魁祸首。
当我们从 PHP 脚本调用 curl_exec() 时,内存泄漏了。一些与处理 SSL 连接相关的 curl 代码出了问题——当我切换到 HTTP 时,泄漏消失了。Curl 的更新日志提到了一些在 7.19.5 中修复的 SSL 内存泄漏(我们使用的是 7.18.2),所以我接下来会尝试一下。
与此同时,我正在以非常低的 MaxRequestsPerChild 运行,以使 Apache 保持在合理范围内。谢谢大家!
答案1
追踪问题的原因可能是一件非常麻烦的事情。如果我遇到这样的问题,我要做的第一件事就是将内存使用率降低MaxRequestsPerChild
到一个非常低的数字(~100-200),看看这是否有影响。如果确实如此,那么你的代码可能在某个循环中泄漏了内存,你需要运行代码审计。
另一件要查看的事情是 Apache 的 fullstatus,看看您是否能找出导致内存泄漏的特定请求。获取可疑进程的 PID 并对其运行 strace。
答案2
星期五晚上 11 点整?这是否对应于备份时间?您的系统是否有可用的 I/O 来为当时的进程和备份提供服务?您的趋势软件是否也趋势 # procs 甚至 apache 记分牌,磁盘 I/O 怎么样?
这第一的我会做的一件事是计算每个进程占用多少内存,然后在 apache 中为 MaxRequests 设置一个合理的限制,以便 $procmem * $procs 不能超过可用内存。我怀疑你的实例需要重新启动,因为 OOM 会引发一场可能(通常)没有多大成效的猎巫行动。你需要确保您的机器能够处理这些繁重的工作,保持在界限之内,不会进入交换状态,当然也不会出现 OOM。如果您有 cronjobs 正在运行,这将会更加困难,如果这些 cronjobs 单方面运行而没有确保其运行安全(即每 5 分钟运行一次的脚本无法检查最后 5 分钟的脚本是否仍在运行),这将极其困难。
现在您已经确保即使出现严重错误,您也不需要重新启动计算机,一切将变得更好。您将能够在这些繁忙时段登录,并使用 top、dstat、free -m、iostat 等了解正在发生的事情。
Matt 的方法可能值得一试,但只能用作故障排除工具,我不建议一直使用这种方法,因为下次查找时,它会使整个问题更难找到。也就是说,它只能真正找出 apache/模块的问题,而不是代码中的任何问题。我想你会同意,很有可能这不是 apache 模块中的某种内存泄漏(假设你使用的是信誉良好的发行版)。
答案3
第一个要问的问题是,通过 Apache 运行的应用程序是什么?
它是你编写的,还是第三方应用程序?
它引用了哪些其他组件/包?
您的包裹是否是最新的?
您的文件中是否有与性能相关的具体内容httpd.conf
?