长PHP脚本运行多次


Long PHP script runs multiple times

我有一个产品数据库,每天早上都会与产品数据同步。

过程非常清楚:

  • 通过查询从数据库中获取所有产品
  • 遍历所有产品,并通过product_id从另一个服务器获取和xml
  • 从xml更新数据
  • 将更改记录到文件中

例如,如果我查询的项目数量很少,但将其限制为500个随机产品,那么一切都会很好。但当我查询所有产品时,我的脚本有时会出错,并开始多次循环。几个小时后,我仍然看到我的日志文件在增长,产品也在添加。

我检查了我能想到的一切,例如:

  • 变量是否在不相互覆盖的情况下未使用两次
  • 函数是否调用自身
  • 这种情况也发生在少量产品上吗
  • 脚本是使用cronjob调用的,设置是否正常。(是)

它之所以特别奇怪,是因为它有时是对的,有时不是。这可能是内存问题吗?

编辑wget -q -O /dev/null http://example.eu/xxxxx/cron.php?operation=sync在webmin中调用了特定的小时和分钟

代码有数百行长。。。

感谢

您有:

  • 已禁用max_execution_time。在流程完成之前,您的脚本不会结束
  • memory_limit已禁用。内存中存储的数据量没有限制

完成了500条记录,没有出现任何问题。这表示脚本在下一次cronjob迭代之前完成了它的过程。例如,如果您的cron每小时运行一次,那么500条记录将在不到一小时的时间内得到处理。

如果您有一个要处理大量记录的cronjob,那么可以考虑向该进程添加锁定机制。只允许脚本运行一次,并在上一个过程完成后重新启动。

在执行php脚本之前,可以将脚本锁作为shell脚本的一部分创建。或者,如果您没有访问服务器的权限,您可以在php脚本中使用数据库锁,类似这样。

class ProductCronJob
{
    protected $lockValue;
    public function run()
    {
        // Obtain a lock
        if ($this->obtainLock()) {
            // Run your script if you have valid lock
            $this->syncProducts();
            // Release the lock on complete
            $this->releaseLock();
        }
    }
    protected function syncProducts()
    {
        // your long running script
    }
    protected function obtainLock()
    {
        $time = new 'DateTime;
        $timestamp = $time->getTimestamp();
        $this->lockValue = $timestamp . '_syncProducts';
        $db = JFactory::getDbo();
        $lock = [
            'lock'         => $this->lockValue,
            'timemodified' => $timestamp
        ];
        // lock = '0' indicate that the cronjob is not active.
        // Update #__cronlock set lock = '', timemodified = '' where name = 'syncProducts' and lock = '0'
//        $result = $db->updateObject('#__cronlock', $lock, 'id');
//        $lock = SELECT * FROM #__cronlock where name = 'syncProducts';
        if ($lock !== false && (string)$lock !== (string)$this->lockValue) {
            // Currently there is an active process - can't start a new one
            return false;
            // You can return false as above or add extra logic as below
            // Check the current lock age - how long its been running for
//            $diff = $timestamp - $lock['timemodified'];
//            if ($diff >= 25200) {
//                // The current script is active for 7 hours.
//                // You can change 25200 to any number of seconds you want.
//                // Here you can send notification email to site administrator.
//                // ...
//            }
        }
        return true;
    }
    protected function releaseLock()
    {
        // Update #__cronlock set lock = '0' where name = 'syncProducts'
    }
}

您的脚本运行了相当长的一段时间(~45m),wget认为它"超时"了,因为您没有返回任何数据。默认情况下,wget的超时值为900s,重试次数为20。因此,首先你可能应该更改你的wget命令来防止这种情况:

wget--trys=0--timeout=0-q-O/dev/nullhttp://example.eu/xxxxx/cron.php?operation=sync

现在,删除超时可能会导致其他问题,因此您可以从脚本中发送(并刷新以强制Web服务器发送)数据,以确保wget不会认为脚本"超时",大约每1000个循环或类似的情况。把它看作一个进度条。。。

请记住,当运行时间接近您的周期时,您将遇到问题,因为2个cron将并行运行。你应该优化你的流程和/或有一个锁定机制?

我看到了两种可能性:-chron调用脚本的频率要高得多-不知怎么的,剧本太长了。

您可以尝试估计循环的单个迭代所花费的时间。这可以用time()来完成。也许结果令人吃惊,也许不是。你可能也可以得到结果的数量。将两者相乘,这样你就可以估计出这个过程需要多长时间。

$productsToSync = $db->loadObjectList();

foreach ($productsToSync AS $product) {

似乎您将每个结果加载到一个数组中。这对大型数据库不起作用,因为显然内存中无法容纳一百万行。你应该一次只得到一个结果。对于mysql,有些方法一次只从资源中获取一个东西,我希望您的方法也允许这样做。

我还看到您在循环的每次迭代中执行另一个查询。这是我尽量避免的事情。也许您可以将其移动到第一个查询结束之后,然后在一个大查询中完成所有这些操作?哦,这可能会影响我的第一个建议。

此外,如果出现问题,调试时要尽量偏执。尽可能多地测量。当性能出现问题时,尽可能多地使用时间。将时间记录在日志文件中。通常你会发现瓶颈。

我自己解决了这个问题。感谢所有的回复!

我的MySQL超时了,这就是问题所在。我一添加:

    ini_set('mysql.connect_timeout', 14400);
    ini_set('default_socket_timeout', 14400);

根据我的脚本,问题停止了。我真的希望这能帮助到别人。我会给所有锁定的答案投赞成票,因为这些答案非常有用!