Подтвердить что ты не робот

Вопросы памяти для длинных сценариев 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 не увеличено и сведено к минимуму, но со временем увеличивается.

4b9b3361

Ответ 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/ содержит некоторые примеры сценариев оболочки для повторного запуска скриптов.