Сделать большую работу по обработке меньше

Это код, который я использую, когда работаю на пути к решению.

public function indexAction() { //id3 options $options = array("version" => 3.0, "encoding" => Zend_Media_Id3_Encoding::ISO88591, "compat" => true); //path to collection $path = APPLICATION_PATH . '/../public/Media/Music/';//Currently Approx 2000 files //inner iterator $dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); //iterator $iterator = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST); foreach ($iterator as $file) { if (!$file->isDir() && $file->getExtension() === 'mp3') { //real path to mp3 file $filePath = $file->getRealPath(); Zend_Debug::dump($filePath);//current results: accepted path no errors $id3 = new Zend_Media_Id3v2($filePath, $options); foreach ($id3->getFramesByIdentifier("T*") as $frame) { $data[$frame->identifier] = $frame->text; } Zend_Debug::dump($data);//currently can scan the whole collection without timing out, but APIC data not being processed. } } } 

Проблема: обрабатывать файловую систему mp3-файлов в нескольких каталогах. Извлеките данные тега id3 в базу данных (3 таблицы) и извлеките обложку из тега в отдельный файл.

Я могу справиться с фактическим извлечением и обработкой данных. Моя проблема связана с выходом.

Благодаря тому, что Zend Framework 1.x обрабатывает буферизацию вывода, вывод индикатора, что файлы обрабатываются, затруднен. В скрипте PHP старого стиля, без буферизации вывода, вы можете распечатать немного html с каждой итерацией цикла и иметь некоторые указания на прогресс.

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

Любая помощь будет оценена по достоинству.

Javascript – это не то решение, которое я ищу. Я чувствую, что это должно быть возможным в конструкциях PHP и ZF 1 MVC.

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

[РЕДАКТИРОВАТЬ]
Хорошо, как насчет некоторых идей о том, как разбить это на более мелкие куски. Обработайте один кусок, зафиксируйте, обработайте следующий кусок, что-то типа. В или из ZF.

[РЕДАКТИРОВАТЬ]
Я начинаю видеть проблему с тем, что я пытаюсь выполнить. Кажется, что буферизация вывода происходит не только в ZF, но и везде происходит от ZF до браузера. Hmmmmm …

Введение

Это типичный пример того, что вы не должны делать, потому что

  • Вы пытаетесь проанализировать ID3 tag с PHP, который медленный и пытается одновременно иметь несколько файлов анализа, определенно сделает его еще медленнее

  • RecursiveDirectoryIterator загрузил бы все файлы в папке и подпапке из того, что я вижу, что нет предела .. это может быть 2,000 сегодня 100,000 на следующий день? Общее время обработки непредсказуемо, и в некоторых случаях это может занять несколько часов

  • Высокая зависимость от одиночной файловой системы, с вашей текущей архитектурой файлы хранятся в локальной системе, поэтому было бы трудно разделить файлы и выполнить правильную балансировку нагрузки

  • Вы не проверяете, была ли ранее извлечена информация о файле, и это результаты. Loop and extraction Duplication

  • No locking system . Это означает, что этот процесс может быть инициирован одновременно, что приводит к общей низкой производительности на сервере

Решение 1: с текущей архитектурой

Мой совет – не использовать loop или RecursiveDirectoryIterator для обработки файлов навалом.

Направьте файл, как только они будут загружены или переданы на сервер. Таким образом, вы работаете только с одним файлом за раз , чтобы увеличить время обработки.

Решение 2: Очередь работы (предлагаемое решение)

Ваша проблема заключается именно в том, для чего предназначена очередь заданий для вас, а также не ограничивается реализацией разбора с PHP . Вы используете C или C++ для повышения производительности

преимущество

  • Перенесите задания на другие машины или процессы, которые лучше подходят для работы
  • Это позволяет выполнять параллельную работу, для обработки баланса нагрузки
  • Уменьшите латентность просмотров страниц в высокопроизводительных веб-приложениях, выполнив многозадачные задачи асинхронно
  • Клиент с несколькими языками в PHP на C

Примеры протестированы

  • ZemoMQ
  • Gearman
  • Beanstalkd

Ожидаемый клиент процесса

  • Подключиться к очереди заданий, например, немецкий
  • Подключение к базе данных, например MongoDB или Redis
  • Петля с папкой
  • Проверить расширение файла
  • Если файл является mp3, генерируйте хэш файла, например. sha1_file
  • Проверьте, отправлен ли файл для обработки
  • отправить хеш, файл на сервер заданий

Ожидаемый сервер процессов

  • Подключиться к очереди заданий, например, немецкий
  • Подключение к базе данных, например MongoDB или Redis
  • Получить хэш / файл
  • Извлечь тег ID3;
  • Обновление базы данных с информацией о тегах ID3

Наконец, эта обработка может выполняться на нескольких серверах параллельно

Одним из решений было бы использование очереди заданий, такой Gearman. Gearman – отличное решение для этой проблемы и легко интегрируется с Zend Framework (http://blog.digitalstruct.com/2010/10/17/integrating-gearman-into-zend-framework/)

Это позволит вам создать рабочего для обработки каждого «патрона», позволяя вашему процессу продолжить разблокировку во время обработки задания, очень удобную для длительных прожетов, таких как обработка музыки / изображений и т . Д. http://gearman.org/index. PHP? ID = Getting_Started

Я не знаком с тем, как работает Zend Framework. Я дам вам общий совет. При работе с процессом, который делает так много итераций и, возможно, в течение длительного времени, обычно рекомендуется, чтобы длительный процесс был перенесен в фоновый процесс. Или, в веб-связанных, переехал в работу cron.

Если процесс, который вы хотите использовать, предназначен для одного сайта, вы можете реализовать что-то подобное в своей cronjob (примечание: грубый псевдокод):

 <?php $targetdir = "/path/to/mp3"; $logdir = "/path/to/log/"; //check if current state is exists. If it does, then previous cronjob is still running //we should stop this process so that it doesn't do duplicated process which might have introduced random bugs if(file_exists($logdir."current-state")){ exit; } //start process, write state to logdir file_put_contents($logdir."current-log", "process started at ".date("Ymd H:i:s")); file_put_contents($logdir."current-state", "started\t".date("Ymd H:i:s")); $dirh = opendir($targetdir); while($file = readdir($dirh)){ //lets ignore current and parent dir if(in_array($file, array('.', '..'))) continue; //do whatever process you want to do here: //you might want to write another log, too: file_put_contents($logdir."current-log", "processing file {$file}", FILE_APPEND); } closedir($dirh); file_put_contents($logdir."current-log", "process finished at ".date("Ymd H:i:s")); //process is finished, delete current-state: unlink($logdir."current-state"); 

Затем, в вашем php-файле для Интернета, вы можете добавить фрагмент текста, сообщает страницу администратора или нижний колонтитул или любую другую страницу, чтобы увидеть прогресс:

 <?php if(file_exists($logdir."current-state")){ echo "<strong>there are background process running.</strong>"; } else { echo "<strong>no background process running.</strong>"; } 

Я должен предложить использовать плагин.

 class Postpone extends Zend_Controller_Plugin_Abstract { private $tail; private $callback; function __construct ($callback = array()) { $this->callback = $callback; } public function setRequest (Zend_Controller_Request_Abstract $request) { /* * We use layout, which essentially contains some html and a placeholder for action output. * We put the marker into this placeholder in order to figure out "the tail" -- the part of layout that goes after placeholder. */ $mark = '---cut-here--'; $layout = $this->getLayout (); $layout->content = $mark; /* * Now we have it. */ $this->tail = preg_replace ("/.*$mark/s", '', $layout->render ()); } public function postDispatch (Zend_Controller_Request_Abstract $request) { $response = $this->getResponse (); $response->sendHeaders (); /* * The layout generates its output to the default section of the response. * This output inludes "the tail". * We don't need this tail shown right now, because we have callback to do. * So we remove it here for a while, but we'll show it later. */ echo substr ($this->getResponse () ->getBody ('default'), 0, - strlen ($this->tail)); /* * Since we have just echoed the result, we don't need it in the response. Do we? */ Zend_Controller_Front::getInstance ()->returnResponse(true); $response->clearBody (); /* * Now to business. * We execute that calculation intensive callback. */ if (! empty ($this->callback) && is_callable ($this->callback)) { call_user_func ($this->callback); } /* * We sure don't want to leave behind the tail. * Output it so html looks consistent. */ echo $this->tail; } /** * Returns layout object */ function getLayout () { $layout_plugin = Zend_Controller_Front::getInstance ()->getPlugin ('Zend_Layout_Controller_Plugin_Layout'); return $layout = $layout_plugin->getLayout (); } } class IndexController extends Zend_Controller_Action { /* * This is a calculation intensive action */ public function indexAction () { /* * Zend_Layout in its current implementation accumulates whole action output inside itself. * This fact hampers out intention to gradually output the result. * What we do here is we defer execution of our intensive calculation in form of callback into the Postpone plugin. * The scenario is: * 1. Application started * 2. Layout is started * 3. Action gets executed (except callback) and its output is collected by layout. * 4. Layout output goes to response. * 5. Postpone::postDispatch outputs first part of the response (without the tail). * 6. Postpone::postDispatch calls the callback. Its output goes stright to browser. * 7. Postpone::postDispatch prints the tail. */ $this->getFrontController () ->registerPlugin (new Postpone (function () { /* * A calculation immigration * Put your actual calculations here. */ echo str_repeat(" ", 5000); foreach (range (1, 500) as $x) { echo "<p>$x</p><br />\n"; usleep(61500); flush(); } }), 1000); } }