Вопросы памяти для длинных сценариев php
Я хочу написать работника для beanstalkd в php, используя контроллер Zend Framework 2. Он запускается через 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
является блокирующим вызовом, и если задание не доступно, этот рабочий будет ждать, пока он не получит ответ от beanstalk.
Мой вопрос: как PHP будет обрабатывать это в долгосрочной перспективе и должен ли я принять какие-либо особые меры предосторожности, чтобы это не блокировалось?
Это я действительно рассматривал и мог быть полезным (но, пожалуйста, исправьте, если я ошибаюсь и добавлю больше, если это возможно):
- Используйте gc_enable() перед запуском цикла
- Используйте gc_collect_cycles() на каждой итерации
- Отменить
$job
на каждой итерации
- Явно отключить ссылки в
__destruct()
от $job
(NB: обновите здесь)
Я провел несколько тестов с произвольными заданиями. Работы, которые я включил, были: "простые", просто установили значение; "longarray", создайте массив из 1000 значений; "продюсер", пусть цикл введет $pheanstalk
и добавит три очереди в очередь (так что теперь есть ссылка с задания на beanstalk); "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 не увеличено и сведено к минимуму, но со временем увеличивается.
Ответы
Ответ 1
Я закончил тестирование моей текущей базовой линии кода для строки, после чего я пришел к следующему:
$job = $this->getLocator()->get($data->name, $params);
Он использует инъекцию зависимостей Zend\Di
, который управляет экземпляром экземпляров через весь процесс. Поэтому после того, как задание было вызвано и могло быть удалено, диспетчер экземпляра все еще сохранил его в памяти. Не используя Zend\Di
для создания экземпляров сразу же, это привело к использованию статической памяти вместо линейной.
Ответ 2
Для безопасности памяти не используйте цикл после каждого задания последовательности в PHP. Но просто создайте простой bash script, чтобы сделать цикл:
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();
// ...
простое право?;)
Ответ 3
Я обычно перезапускал script регулярно, хотя вам не нужно делать это после выполнения каждого задания (если вы этого не хотите, и полезно очистить память). Например, вы можете запускать до 100 заданий или больше за один раз или до использования script, например, 20 МБ ОЗУ, а затем выйти из script, чтобы быть мгновенно повторно запущен.
Мой блогпост на http://www.phpscaling.com/2009/06/23/doing-the-work-elsewhere-sidebar-running-the-worker/ содержит некоторые примеры сценариев оболочки для повторного запуска скриптов.