4

这是我在寻找解决方案时使用的代码。

 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 一直到浏览器。嗯……

4

4 回答 4

7

介绍

这是你不应该做的典型例子,因为

  • 您正在尝试ID3 tag使用速度很慢的 PHP 进行解析,并且尝试一次拥有多个解析文件肯定会使它变得更慢

  • RecursiveDirectoryIterator会从我看到的没有限制的情况下加载文件夹和子文件夹中的所有文件..2,000今天可以是100,000第二天吗?总处理时间是不可预测的,在某些情况下这肯定需要几个小时

  • 对单个文件系统的高度依赖,在您当前的架构下,文件存储在本地系统中,因此很难拆分文件并进行适当的负载平衡

  • 您没有检查文件信息是否已经被提取过并且这个结果Loop and extraction Duplication

  • No locking system.. 这意味着这个过程可以同时启动,导致服务器性能普遍下降

解决方案 1:使用当前架构

我的建议是不要批量使用loopRecursiveDirectoryIterator处理文件。

将文件上传传输到服务器后立即定位文件。这样,您一次只能处理一个文件,这样可以分散处理时间。

解决方案 2:作业队列(建议的解决方案)

您的问题正是Job Queue 的设计目的,您也不限于使用 PHP.. 实现解析,您可以利用 CC++提高性能

优势

  • 将作业转移到更适合完成工作的其他机器或流程
  • 它允许您并行工作,以进行负载平衡处理
  • 通过异步运行耗时的任务来减少大容量 Web 应用程序中页面查看的延迟
  • PHP服务器中的多语言客户端C

示例已测试

预期流程客户

  • 连接到作业队列,例如德语
  • 连接到数据库,例如 MongoDB 或 Redis
  • 使用文件夹路径循环
  • 检查文件扩展名
  • 如果文件是 mp3 ,则生成文件哈希,例如。sha1_file
  • 检查文件是否已发送进行处理
  • 发送哈希,文件到作业服务器

预期的进程服务器

  • 连接到作业队列,例如德语
  • 连接到数据库,例如 MongoDB 或 Redis
  • 接收哈希/文件
  • 提取 ID3 标签;
  • 使用 ID3 标签信息更新数据库

最后,这个处理可以在多台服务器上并行完成

于 2012-11-15T08:06:26.163 回答
2

一种解决方案是使用作业队列,例如 Gearman。Gearman 是此类问题的出色解决方案,并且可以轻松与 Zend Framework 集成 (http://blog.digitalstruct.com/2010/10/17/integrating-gearman-into-zend-framework/)

它将允许您创建一个工作人员来处理每个“卡盘”,允许您的进程在处理作业时继续畅通无阻,对于长时间运行的进程非常方便,例如音乐/图像处理等http://gearman.org/index。 php?id=getting_started

于 2012-11-13T16:03:06.517 回答
1

我不熟悉 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("Y-m-d H:i:s"));
file_put_contents($logdir."current-state", "started\t".date("Y-m-d 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("Y-m-d H:i:s"));

//process is finished, delete current-state:
unlink($logdir."current-state");

接下来,在 web 的 php 文件中,您可以将代码片段添加到管理页面、页脚或任何您想要的页面,以查看进度:

<?php

if(file_exists($logdir."current-state")){
    echo "<strong>there are background process running.</strong>";
} else {
    echo "<strong>no background process running.</strong>";
}
于 2012-11-13T05:54:47.607 回答
1

我应该建议使用插件。

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);
    }
}
于 2012-11-13T19:51:25.783 回答