13

我想使用Zend Framework 2控制器在php中为beanstalkd编写一个worker。它通过CLI启动,并将永远运行,要求从诸如this example等beanstalkd工作。长时间运行的PHP脚本的内存考虑因素

在简单的伪类似的代码:

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如何处理这个长期的问题,我应该采取一些特殊的预防措施以防止阻塞?

这个我没考虑,可能是有益的(但请大家指正,如果我错了,并添加更多,如果可能的话):在每次迭代

  • 开始循环
  • 使用gc_collect_cycles()

    1. 使用gc_enable()在每次迭代中取消设置$job
    2. 明确地取消设置__destruct()中的参考$job

    (注:从这里更新)

    我并运行一些测试任意工作。我包括的工作是:“简单”,只需设定一个值; “longarray”,创建一个包含1,000个值的数组; “生产者”,让循环注入$pheanstalk并添加三个simplejob到队列(所以现在有一个从作业到豆茎的参考); “locatoraware”,其中给出Zend\Di\Locator并且实例化所有作业类型(尽管未被调用)。我向队列中添加了10,000个作业,然后我将所有作业保留在队列中。

    结果 “simplejob”(每1000个作业存储器的消耗,与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 杠杆和保持在最低限度,但随着时间的推移而增加。

  • +0

    这是一个有趣的问题,我添加了一些关于使用'gc_collect_cycles'的相关研究http://stackoverflow.com/questions/38850391/when-does-php-run-garbage-collection-in-long-running-scripts/ 38850392#38850392 – mcfedr 2016-08-29 12:10:54

    回答

    2

    我结束了对基准线我当前的代码基线,在这之后我来到了这一点:

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

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

    +0

    我也面临类似的问题。 你认为下面的方法不会帮助吗? -gc_enable()开始在每次迭代 -Unset $工作循环 - 使用gc_collect_cycles()在__destruct()从$工作 – 2013-07-06 07:14:56

    +0

    每次迭代 -Explicitly未设置引用只是要确保前你不守的一个实例容器内的类。我结束了使用ServiceManager并将其共享行为设置为false。 – 2013-07-09 16:18:14

    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(); 
    
    
    // ... 
    

    简单吧? ;)

    +1

    我想保持在PHP内的控制。如果在执行任务期间出现问题,bash不知道这一点,只是开始下一个工作。在这种情况下,你对它的控制力会减弱。另外,使用ZF2 cli应用程序,您可以直接通过(例如)'app.php worker reserve --watch default --sleep-between 100 --log。/ data/log/worker'来调用控制器,这就是我我喜欢这样做。 – 2012-04-03 08:26:14

    +0

    你的工作序列是否依赖于其他工作(在循环中)?如果是这样,那么你必须使用完整的PHP解决方案。如果每个工作都是独立的,恕我直言bash和php组合是避免PHP内存泄漏的最佳选择。 – Superbiji 2012-04-04 07:15:53

    1

    我通常经常重新启动脚本 - 尽管在每个作业运行后你都不必这样做(除非你想,而且清除内存很有用)。例如,您可以一次运行多达100个作业或更多作业,或者直到脚本使用了20MB RAM,然后退出脚本,立即重新运行。

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

    +1

    在这里,内存的考虑因素还包括使用bash来控制序列而不是php本身。我希望只有一个PHP的解决方案,但看起来它可能是不可能的。然而,退出代码策略似乎对流程有更多的控制。 – 2012-04-03 20:36:55