我想使用 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 将如何长期处理这个问题,我应该采取任何特殊的预防措施来防止它被阻塞?
我确实考虑过这一点并且可能会有所帮助(但如果我错了,请更正并尽可能添加更多内容):
- 在开始循环之前使用gc_enable()
- 在每次迭代中使用gc_collect_cycles()
$job
在每次迭代中取消设置__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 中没有被利用并保持在最低限度,但随着时间的推移而增加。