13

Я хочу написать работника для beanstalkd в php, используя контроллер Zend Framework 2. Он запускается через CLI и будет работать вечно, требуя рабочих мест от beanstalkd, таких как this example.Вопросы памяти для длинных сценариев 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 будет обрабатывать это в долгосрочной перспективе, и я должен принять любую специальную меру предосторожности, чтобы это не блокировалось?

Это я рассмотреть и может быть полезным (но, пожалуйста, поправьте, если я ошибаюсь, и добавить больше, если это возможно):

  1. Использование gc_enable() перед началом цикла
  2. Использование gc_collect_cycles() в каждой итерации
  3. Unset $job в каждой итерации
  4. Явное неустановленных ссылок в __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 не заемных средств и сведен к минимуму, но со временем увеличивается.

+0

Это интересный вопрос, я добавил некоторые связанные исследования об использовании 'gc_collect_cycles' http://stackoverflow.com/questions/38850391/when-does-php-run-garbage-collection-in-long-running-scripts/ 38850392 # 38850392 – mcfedr

ответ

2

Я в конечном итоге бенчмаркинг мой текущий код базовой линии для линии, после чего я пришел к этому:

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

Он использует инъекции Zend\Di зависимостей, которые менеджер экземпляра отслеживает экземпляры через весь процесс. Поэтому после того, как задание было вызвано и могло быть удалено, диспетчер экземпляра все еще сохранил его в памяти. Не используя Zend\Di для создания экземпляров сразу же, это привело к статическому потреблению памяти вместо линейного.

+0

Я также сталкиваюсь с аналогичной проблемой. Вы думаете, что чуть ниже методы не помогают? -gc_enable() перед началом цикла -use gc_collect_cycles() в каждой итерации -Unset $ работа в каждой итерации -Explicitly неустановленных ссылок в __destruct() от $ задания –

+0

Просто убедитесь, что вы не держите экземпляр класс внутри контейнера. Я закончил использование ServiceManager и установил его общее поведение в false. –

1

Для обеспечения безопасности памяти не используйте цикл после каждого задания последовательности в PHP. Но просто создать простой Баш скрипт для зацикливания:

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 - между 100 --log./Data/log/worker', что и есть то, что я «Мне нравится делать. –

+0

Имеет ли ваша последовательность заданий зависимость от другого задания (в цикле)? если это так, то вам нужно использовать полное PHP-решение. Если каждая независимость от работы, комбинация IMHO bash & php - лучший способ избежать утечек памяти PHP. – Superbiji

1

Я обычно перезапускал сценарий регулярно - хотя вам не нужно делать это после выполнения каждой задачи (если вы этого не хотите, и полезно очистить память). Например, вы можете запускать до 100 заданий или больше за один раз или до того, как скрипт использовал 20MB RAM, а затем выйдите из сценария, чтобы мгновенно перезапустить.

В моем блоге на http://www.phpscaling.com/2009/06/23/doing-the-work-elsewhere-sidebar-running-the-worker/ есть примеры сценариев оболочки для повторного запуска скриптов.

+1

Также здесь соображения памяти покрываются использованием bash для управления последовательностью вместо самого php. Я надеялся на решение только для php, но, похоже, это было бы невозможно. Однако стратегия кодов выхода дает больше контроля над потоком. –