长PHP脚本运行多次

时间:2015-03-27 15:23:51

标签: php cron

我有一个产品数据库,可以在早上与产品数据同步。

这个过程非常明确:

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

如果我查询少量商品,但将其限制为500件随机产品,一切都很顺利。但是当我查询所有产品时,我的脚本SOMETIMES继续使用fritz并开始多次循环。几个小时后,我仍然看到我的日志文件在增长,产品正在增加。

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

  • 变量未使用两次而不会互相覆盖
  • 该函数是否自行调用
  • 是否会出现少量产品:否。
  • 使用cronjob调用脚本,设置正常。 (是)

使它特别奇怪的原因是它有时是正确的,有时它不会。这可能是一些记忆问题吗?

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

代码长达数百行...

由于

4 个答案:

答案 0 :(得分:6)

你有:

  • 禁用了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'
    }
}

答案 1 :(得分:5)

你的脚本运行了很长时间(约45米),并且因为你没有返回任何数据而认为它是“超时”。默认情况下,wget将具有900s的超时值和20的重试次数。因此,首先您应该更改wget命令以防止这种情况:

wget --tries = 0 --timeout = 0 -q -O / dev / null http://example.eu/xxxxx/cron.php?operation=sync

现在删除超时可能会导致其他问题,因此您可以从脚本发送(和flush强制Web服务器发送它)数据,以确保wget不认为脚本“超时” ,每1000个循环或类似的东西。把它想象成一个进度条......

请记住,当运行时间接近您的时间段时,您将遇到问题,因为2个crons将并行运行。你应该优化你的过程和/或有锁机制吗?

答案 2 :(得分:4)

我看到两种可能性: - chron更频繁地调用脚本 - 脚本花了太长时间。

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

$productsToSync = $db->loadObjectList();

foreach ($productsToSync AS $product) {

似乎你将每个结果加载到一个数组中。这对大型数据库不起作用,因为显然有一百万行不适合内存。你应该一次只得到一个结果。使用mysql有一些方法可以一次从资源中获取一个东西,我希望你的方法允许相同的。

我还看到你在循环的每次迭代中执行另一个查询。这是我试图避免的。也许你可以在第一个查询结束后将其移动到一个大查询中执行所有这些操作? otoh这可能会咬我的第一个建议。

如果出现问题,请在调试时尝试偏执。尽可能多地衡量。在遇到性能问题时,尽可能多的时间。把时间放在你的日志文件中。通常你会发现瓶颈。

答案 3 :(得分:3)

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

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

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

到我的脚本问题停止了。我真的希望这有助于某人。我会提供所有锁定答案,因为这些答案非常有用!