16

我想使用 Zend Framework 2 控制器在 php 中为beanstalkd编写一个 worker。它通过 CLI 启动并将永远运行,像这个示例一样从 beanstalkd 请求工作。

在简单的伪类代码中:

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是一个阻塞调用,如果没有可用的作业,这个 worker 将等待直到它从 beanstalk 得到任何响应。

我的问题:php 将如何长期处理这个问题,我应该采取任何特殊的预防措施来防止它被阻塞?

我确实考虑过这一点并且可能会有所帮助(但如果我错了,请更正并尽可能添加更多内容):

  1. 在开始循环之前使用gc_enable()
  2. 在每次迭代中使用gc_collect_cycles()
  3. $job在每次迭代中取消设置
  4. __destruct()从 a中显式取消设置引用$job

(注意:从这里更新)

我确实对任意作业进行了一些测试。我包括的工作是:“简单”,只需设置一个值;“longarray”,创建一个包含 1,000 个值的数组;“生产者”,让循环注入$pheanstalk并将三个 simplejobs 添加到队列中(因此现在有一个从 job 到 beanstalk 的引用);“locatoraware”,其中Zend\Di\Locator给出了 a 并且所有作业类型都被实例化(尽管没有被调用)。我将 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 中没有被利用并保持在最低限度,但随着时间的推移而增加。

4

3 回答 3

2

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

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

于 2012-04-02T17:08:27.687 回答
2

我最终对我当前的代码基准线进行了基准测试,之后我得出了这个结论:

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

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

于 2012-04-07T21:04:33.883 回答
1

For memory safety, don't use looping after each sequence job in PHP. But just create simple bash script to do looping:

while [ true ] ; do
    php  do_jobs.php 
done

Hey there, with do_jobs.php contains something like:

// ...

$data   = $beanstalk->reserve();

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

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


// ...

simple right? ;)

于 2012-04-02T15:20:59.493 回答