调试长时间运行的PHP脚本

14

我有一个作为cron作业的php脚本,广泛使用第三方代码。 脚本本身有几千行代码。 基本上它是数据导入 / 处理脚本。(将JSON导入到MySQL中,但它也进行了许多HTTP调用和一些SOAP)。

现在,性能正在下降。 当测试大约100个记录时,性能还可以,在10-20分钟内完成。 当运行整个导入(大约1600条记录)时,每个记录的导入平均时间稳步增长,并且整个过程需要超过24小时,因此至少比预期的时间长5倍。

内存似乎不是问题,使用情况如预期那样增长,没有出现意外峰值。

因此,我需要调试以找出瓶颈所在。 它可能是脚本、基础代码库、php本身、数据库、操作系统或网络部分的某个问题。 我怀疑现在有某种缓存在某处,它的未命中率接近100%,表现不佳。

我无法使用XDebug,配置文件增长速度太快,难以处理。

因此问题是:我该如何调试这种脚本?

PHP版本:5.4.41 操作系统:Debian 7.8 如果必要,我可以拥有root权限并安装工具。 但它是生产服务器,理想情况下调试不应该对其造成太大影响。


有没有绝对的能力创建一个开发环境,以便您可以计时每个部分并计算运行1条记录需要多长时间?另外,您是在什么时间运行这个程序?是在白天还是在晚上开始运行? - Cayce K
11个回答

14

是的,这是可能的并且您可以使用Kint(PHP调试脚本)

什么是Kint? Kint for PHP是一种工具,旨在以最好的方式呈现您的调试数据。

换句话说,它是var_dump()和debug_backtrace()的增强版。易于使用,但功能强大且可定制。 是开发工具箱中必不可少的补充。

还在迷失吗?您可以使用它来查看变量内部的内容。

enter image description here

也充当debug_backtrace替代者 enter image description here

您可以在此下载在这里下载

所有文档和帮助都在这里

此外,它还支持几乎所有PHP框架

  • CodeIgniter
  • Drupal
  • Symfony
  • Symfony 2
  • WordPress
  • Yii
  • framework
  • Zend Framework

祝您一切顺利.... :)


8

有三件事情需要注意:

  1. 设置 IDE 以便您可以逐行调试 PHP 脚本
  2. 在脚本中添加一些日志记录
  3. 查找 MySQL 中的长时间运行查询

调试选项 #2 是最简单的。由于这是作为 cron 作业运行的,因此您可以在脚本中添加大量 echo

<?php

function log_message($type, $message) {
    echo "[{strtoupper($type)}, {date('d-m-Y H:i:s')}] $message";
}

log_message('info', 'Import script started');

// ... the rest of your script

log_message('info', 'Import script finished');

然后在cron作业命令中将stdout管道输出到日志文件。

01 04 * * * php /path/to/script.php >> /path/to/script.log

现在你可以在脚本中添加log_message('info|warn|debug|error', 'Message here'),以便至少了解性能问题出现的位置。
调试选项#3是直接在MySQL中进行调查工作。你的某个查询可能需要很长时间,并且可能会显示在MySQL的长时间运行查询实用程序中。

7

性能分析工具:

有一款名为 Blackfire 的 PHP 性能分析工具目前处于公测阶段。有关如何分析 CLI 应用程序的具体文档。一旦收集了分析数据,您可以在一个漂亮的用户界面中使用时间度量值分析应用程序控制流:

Blackfire tool

内存消耗异常:

内存似乎不是问题,使用情况正常增长,没有出现异常峰值。

内存使用量的增长实际上听起来很可疑!如果当前数据集不依赖于导入的所有先前数据集,则增加的内存使用量很可能意味着所有导入的数据集都保存在内存中,这是不好的。PHP 也可能经常尝试进行垃圾回收,只是发现没有什么可以从内存中删除的东西。特别是长时间运行的 CLI 任务受到影响,因此请务必阅读发现此行为的博客文章


嗯,这很有趣。我测试了短时间运行(1600个记录中的300个),使用gc的时间更短(398秒与不使用gc的339秒相比),但内存使用量激增:使用gc时为112 M,而不使用gc时为572 M。因此,似乎不使用gc选项是不可行的 - 我不能允许脚本在2G服务器上使用2.5 G的内存。 - ts.
由于关闭GC时内存使用量激增,打开GC时似乎会释放内存,这是一个好迹象 - 尽管不是很有用。作为下一步,我建议使用Blackfire分析器来定位脚本中可疑的部分。Blackfire团队有一篇不错的博客文章,解释了他们如何使用Blackfire来分析CLI工具的内存/CPU使用情况。或者您的服务器无法使用Blackfire吗? - Fabian Kleiser

6
使用 strace 从系统的角度查看程序的基本操作,是否在I/O操作等方面挂起?在遇到任何类型的Linux应用程序性能问题时,strace 应该是您尝试的第一件事情。没有人能躲过它! ;)
如果您发现程序在网络相关调用(如 connectreadfrom 等)中挂起,这意味着网络通信在连接或等待响应时在某个点上挂起,那么您可以使用 tcpdump 进行分析。
使用以上方法,您应该能够找出大多数常见的性能问题。请注意,您甚至可以使用 -p PIDstrace 附加到正在运行的任务上。
如果以上方法无效,我建议使用 xdebug 对脚本进行分析。您可以使用像 KCachegrind 这样的工具来分析分析器输出。

5
尽管没有规定,但如果我的猜测是正确的,您似乎是一次处理一个记录,但在一个大的cron中。
例如:获取记录#1,以某种方式对其进行处理,添加值,重新格式化,然后保存它,然后转到记录#2。
我建议将这个大的cron拆分成几个小的cron。
Cron#1:获取所有记录,并在本地(服务器)缓存所有重要数据。如果完成此阶段,请设置标志。
Cron#2:现在您拥有所需的数据,请进行处理和添加值,并缓存该输出。如果完成此阶段,请设置标志。
Cron#3:重新格式化数据并存储它。删除所有文件。
这种“分而治之”的方法将减轻您的调试困难,并带来更好的理解实际发生了什么,作为奖励,还可以让您重新运行cron 2。
我曾多次这样做,对我来说,日志记录是识别代码弱点,识别数据质量差的错误假设以及提示延迟引起问题的位置的关键。

4

过去在进行网络相关的任务时,我遇到了一些奇怪的减速情况。基本上,我发现在手动测试期间,系统非常快,但是当让其无人值守时,它完成的工作量并不如我所希望的那么多。

在我的情况下,我发现问题出在了默认的网络超时设置上,很多网络请求会因此超时。

虽然不是外部工具,但是你可以使用两个microtime(TRUE)请求之间的差异来计时代码段。为了保持记录的简洁,您可以设置一个标志限制,仅在每个事件减少后标志未降至零时才测试时间。您可以为不同的代码段甚至不同的时间限制设置单独的标志。

$flag['name'] = 10;  // How many times to fire
$slow['name'] = 0.5; // How long in seconds before it's a problem?

$start = microtime(TRUE);
do_something($parameters);
$used  = microtime(TRUE) - $start;
if ( $flag['name'] && used >= $slow['name'] )
{
  logit($parameters);
  $flag['name']--;
}

如果你输出了哪个URL或其他数据/事件需要处理时间过长的信息,那么你可以稍后深入研究这个特定项目,看看它如何在你的代码中造成问题。当然,这假设单个项目正在导致你的问题,而不仅仅是随着时间的推移出现的一般性减速。
编辑:
我现在看到这是一个生产服务器。这使得编辑代码变得不那么愉快。你可能想要使与代码集成的过程最小化,将测试逻辑和可能支持的标记/标志以及数量放在外部文件中。
setStart('flagname');
// Do stuff to be checked for speed here
setStop('flagname',$moredata);

为了最大程度的稳健性,方法/函数必须确保处理未知标签、缺少参数等情况。

4

xdebug_print_function_stack是一种选项,但您还可以创建“函数跟踪”。有三种输出格式。一种用于人类可读的跟踪,另一种更适合计算机程序,因为它更容易解析,最后一种使用HTML格式化跟踪。

http://www.xdebug.org/docs/execution_trace

3
好的,基本上你有两种可能性 - 要么是无效的PHP代码,要么是无效的MySQL代码。根据你所说的,很可能是单独插入到索引表中的大量记录,导致插入时间激增。你应该禁用索引并在插入后重建它们,或者优化插入代码。
但是,关于工具。
您可以配置系统自动记录慢速MySQL查询: https://dev.mysql.com/doc/refman/5.1/en/slow-query-log.html 您也可以对PHP脚本执行相同的操作,但需要PHP-FPM环境(您可能有Apache)。 https://rtcamp.com/tutorials/php/fpm-slow-log/ 这些工具非常强大和多功能。
附言:100个记录需要10-20分钟似乎很多。

但是 PHP 的慢日志会随着 xdebug 的分析文件增长而变得完全无法使用吗?顺便说一句,大多数记录都会扩展到几百个子记录,并可能进行 HTTP 请求以收集外部信息;但我同意这确实很多。 - ts.
它将包含所有慢查询的有用信息。分析工作由你来完成。当然,为了防止其过度增长,应该设置适当的日志轮换。 - Samvel Avanesov

2
您可以使用https://github.com/jmartin82/phplapse来记录应用程序在确定时间内的活动情况。
例如,可以以下面的方式开始记录“n”个迭代后的活动:
phplapse_start();

在下一次迭代中停止它,使用以下代码:
phplapse_stop();

通过这个过程,您可以创建一个执行快慢的快照。

(我是该项目的作者,请随时与我联系以改进功能)


1
我每晚都会运行类似的东西(用于更新数据库的定时任务)。我发现最可靠的调试方式是在数据库中设置一个日志表,定期插入/更新包含关于每个记录和任何有用信息的多维数组的json字符串。这样,如果你的定时任务没有完成,你仍然可以得到关于任务进度和发生情况的详细信息。然后,你可以编写一个简单的页面来提取json字符串,将其转换回数组,并打印有用的数据,包括时间和通过的测试等。当你看到某些问题时,你可以集中精力将更多的信息从该区域放入json字符串中。

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