长时间运行的PHP脚本的内存注意事项

时间:2012-04-02 13:56:38

标签: php memory garbage-collection command-line-interface beanstalkd

我想使用Zend Framework 2控制器在php中为beanstalkd编写一个worker。它通过CLI启动并将永久运行,从beanstalkd请求作业this example

在简单的类伪代码中:

while (true) {
    $data   = $beanstalk->reserve();

    $class  = $data->class;
    $params = $data->params;

    $job    = new $class($params);
    $job();
}

$job当然有一个__invoke()方法。但是,这些工作中的某些事情可能会持续很长时间。有些可能会运行相当大的内存。有些人可能注入$beanstalk对象,自己开始新的工作,或者有一个Zend\Di\Locator实例从DIC中提取对象。

我担心长期生产环境的这种设置,因为可能会出现循环引用(此时)我没有明确“做”任何垃圾收集,而此操作可能会持续数周/数月/年*

*)在beanstalk中,reserve是一个阻塞调用,如果没有可用的作业,该工作程序将等待它从beanstalk返回任何响应。

我的问题:php如何长期处理这个问题,我应该采取什么特别的预防措施来防止这种情况发生?

我确实考虑过并且可能会有所帮助(但如果我错了请更正并在可能的情况下添加更多内容):

  1. 在开始循环之前使用gc_enable()
  2. 在每次迭代中使用gc_collect_cycles()
  3. 在每次迭代中取消设置$job
  4. __destruct()
  5. 明确取消$job中的引用

    (注意:从此处更新)

    我确实用任意作业进行了一些测试。我包括的工作是:“简单”,只是设置一个值; “longarray”,创建一个包含1,000个值的数组; “producer”,让循环注入$pheanstalk并向队列添加三个simplejobs(所以现在有一个从job到beanstalk的引用); “locatoraware”,其中给出Zend\Di\Locator并且实例化所有作业类型(虽然未调用)。我在队列中添加了10,000个作业,然后我将所有作业保留在队列中。

    “simplejob”的结果(每1,000个作业的内存消耗量,memory_get_usage()

    0:     56392
    1000:  548832
    2000:  1074464
    3000:  1538656
    4000:  2125728
    5000:  2598112
    6000:  3054112
    7000:  3510112
    8000:  4228256
    9000:  4717024
    10000: 5173024
    

    挑选随机工作,测量与上述相同。分布:

    ["Producer"] => int(2431)
    ["LongArray"] => int(2588)
    ["LocatorAware"] => int(2526)
    ["Simple"] => int(2456)
    

    内存:

    0:     66164
    1000:  810056
    2000:  1569452
    3000:  2258036
    4000:  3083032
    5000:  3791256
    6000:  4480028
    7000:  5163884
    8000:  6107812
    9000:  6824320
    10000: 7518020
    

    上面的执行代码更新为:

    $baseMemory = memory_get_usage();
    gc_enable();
    
    for ( $i = 0; $i <= 10000; $i++ ) {
        $data = $bheanstalk->reserve();
    
        $class = $data->class;
        $params = $data->params;
    
        $job = new $class($params);
        $job();
    
        $job = null;
        unset($job);
    
        if ( $i % 1000 === 0 ) {
            gc_collect_cycles();
            echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "<br>";
        }
    }
    

    正如大家所注意到的那样,内存消耗在php 而不是中被利用并保持在最低限度,但会随着时间的推移而增加。

3 个答案:

答案 0 :(得分:2)

我通常会定期重新启动脚本 - 虽然您不必在每个作业运行后执行此操作(除非您愿意,并且清除内存很有用)。例如,您可以一次运行多达100个或更多作业,或直到脚本使用了20MB RAM,然后退出脚本,立即重新运行。

我在http://www.phpscaling.com/2009/06/23/doing-the-work-elsewhere-sidebar-running-the-worker/的博客文章中有一些重新运行脚本的shell脚本。

答案 1 :(得分:2)

我最终对我当前的代码库行进行基准测试,之后我来到了这个:

$job = $this->getLocator()->get($data->name, $params);

它使用Zend\Di依赖注入,实例管理器通过整个过程跟踪实例。因此,在调用作业并将其删除后,实例管理器仍将其保留在内存中。不使用Zend\Di来实例化作业会立即导致静态内存消耗,而不是线性消耗。

答案 2 :(得分:1)

为了确保内存安全,请不要在PHP中的每个序列作业后使用循环。但只需创建简单的bash脚本来进行循环:

while [ true ] ; do
    php  do_jobs.php 
done

嘿那里,do_jobs.php包含的内容如下:

// ...

$data   = $beanstalk->reserve();

$class  = $data->class;
$params = $data->params;

$job    = new $class($params);
$job();


// ...

简单吧? ;)