比较目录状态或散列以获得乐趣和利润的最快方法

时间:2010-12-03 08:54:23

标签: php linux bash hash

我们有一个PHP应用程序,并且认为让应用程序知道自上次执行以来它的构成是否有变化可能是有利的。主要是由于管理缓存等,并且知道我们的应用程序有时会被不记得清除缓存更改的人访问。 (改变人民是明显的答案,但唉,不是真的可以实现)

我们已经提出了这个问题,这是我们成功实现的最快速度,在一台典型项目的开发者机器上平均运行0.08。我们用shasum,md5和crc32进行了实验,这是最快的。我们基本上md5ing每个文件的内容,并md5'输出。安全性不是问题,我们只对通过不同的校验和检测文件系统更改感兴趣。

time (find application/ -path '*/.svn' -prune -o -type f -print0 | xargs -0 md5 | md5)

我想问的是,这可以进一步优化吗?

(我意识到修剪svn会有成本,但是发现组件花费的时间最少,因此它将非常小。我们正在对工作副本进行测试)

6 个答案:

答案 0 :(得分:12)

您可以使用inotify extension通知文件系统修改。

可以使用pecl安装:

pecl install inotify

或手动(下载,phpize&& ./configure&& make&& make install照常安装)。

这是linux inotify系统调用的原始绑定,可能是linux上最快的解决方案。

请参阅此简单tail实施示例:http://svn.php.net/viewvc/pecl/inotify/trunk/tail.php?revision=262896&view=markup


如果您需要更高级别的库,或者支持非Linux系统,请查看Lurker

它可以在任何系统上运行,并且可以在可用时使用。

请参阅README

中的示例
$watcher = new ResourceWatcher;
$watcher->track('an arbitrary id', '/path/to/views');

$watcher->addListener('an arbitrary id', function (FilesystemEvent $event) {
    echo $event->getResource() . 'was' . $event->getTypeString();
});

$watcher->start();

答案 1 :(得分:6)

您可以使用与文件名和时间戳相同的技术来代替文件内容:

find . -name '.svn' -prune -o -type f -printf '%m%c%p' | md5sum

这比读取和散列每个文件的内容要快得多。

答案 2 :(得分:5)

主动搜索更改的内容,为什么不在某些内容发生变化时收到通知。看看PHP's FAM - File Alteration Monitor API

  

FAM监控文件和目录,通知感兴趣的应用程序更改。有关FAM的更多信息,请访问»http://oss.sgi.com/projects/fam/。 PHP脚本可以使用此扩展提供的功能指定要监视的FAM文件列表。当打开从任何应用程序到它的第一个连接时,将启动FAM进程。在关闭所有连接后退出。

CAVEAT:在机器上需要一个额外的守护程序,并且PECL扩展程序没有维护。

答案 3 :(得分:4)

我们不想使用FAM,因为我们需要在服务器上安装它,但这并不总是可行的。有时客户坚持我们在他们破碎的基础设施上部署。由于它已经停止使用,因此很难获得批准的更改。

提高问题原始版本速度的唯一方法是确保您的文件列表尽可能简洁。 IE只会散列真正重要的目录/文件(如果已更改)。删除不相关的目录可以大大提高速度。

过去,应用程序正在使用该函数检查是否有更改,以便执行缓存清除(如果有)。由于我们不想在执行此操作时暂停应用程序,因此最好将这种事情作为使用fsockopen的异步过程进行仔细研究。这样可以提供最佳的“速度提升”,只要注意竞争条件。

将此标记为“答案”并提升FAM答案。

答案 4 :(得分:2)

因为你有svn,为什么不通过修订。我意识到你正在跳过svn文件夹,但我想你是为了速度优势而做的,并且你没有在生产服务器中修改过的文件......

如果说,你不必重新发明轮子。

只需查看从目录索引读取的元数据(修改时间戳,文件大小等),就可以加快进程速度

一旦发现差异(理论上应该将时间减少一半),你也可以停止等等。有很多。

老实说,我认为在这种情况下最好的方法就是使用现有的工具。

linux工具diff有一个-q选项(快速)。

您还需要将它与递归参数-r一起使用。

diff -r -q dir1/ dir2/

它使用了很多优化,我非常怀疑你可以毫不费力地对它进行显着的改进。

答案 5 :(得分:2)

您应该使用Inotify它的快速且易于配置,直接来自bashphp的多个选项专用于此任务的简单node-inotify实例

但是Inotify没有佩戴在Windows上,但是您可以轻松地使用FileSystemWatcherFindFirstChangeNotification编写命令行应用程序并通过exec调用

如果您只查找PHP solution,那么它非常困难,您可能无法获得性能,因为唯一的方法是连续扫描该文件夹

这是简单实验

  • 不要在生产中使用
  • 无法管理大型文件集
  • 不支持文件监控
  • 仅支持NEW,DELETED和MODIFIED
  • 不支持递归

示例

if (php_sapi_name() !== 'cli')
    die("CLI ONLY");

date_default_timezone_set("America/Los_Angeles");

$sm = new Monitor(__DIR__ . "/test");

// Add hook when new files are created
$sm->hook(function ($file) {
    // Send a mail or log to a file
    printf("#EMAIL NEW FILE %s\n", $file);
}, Monitor::NOTIFY_NEW);

// Add hook when files are Modified
$sm->hook(function ($file) {
    // Do monthing meaningful like calling curl
    printf("#HTTP POST  MODIFIED FILE %s\n", $file);
}, Monitor::NOTIFY_MODIFIED);

// Add hook when files are Deleted
$sm->hook(function ($file) {
    // Crazy ... Send SMS fast or IVR the Boss that you messed up
    printf("#SMS DELETED FILE %s\n", $file);
}, Monitor::NOTIFY_DELETED);

// Start Monitor
$sm->start();

使用缓存

// Simpe Cache
// Can be replaced with Memcache
class Cache {
    public $f;

    function __construct() {
        $this->f = fopen("php://temp", "rw+");
    }

    function get($k) {
        rewind($this->f);
        return json_decode(stream_get_contents($this->f), true);
    }

    function set($k, $data) {
        fseek($this->f, 0);
        fwrite($this->f, json_encode($data));
        return $k;
    }

    function run() {
    }
}

实验班

// The Experiment
class Monitor {
    private $dir;
    private $info;
    private $timeout = 1; // sec
    private $timeoutStat = 60; // sec
    private $cache;
    private $current, $stable, $hook = array();
    const NOTIFY_NEW = 1;
    const NOTIFY_MODIFIED = 2;
    const NOTIFY_DELETED = 4;
    const NOTIFY_ALL = 7;

    function __construct($dir) {
        $this->cache = new Cache();
        $this->dir = $dir;
        $this->info = new SplFileInfo($this->dir);
        $this->scan(true);
    }

    public function start() {
        $i = 0;
        $this->stable = (array) $this->cache->get(md5($this->dir));

        while(true) {
            // Clear System Cache at Intervals
            if ($i % $this->timeoutStat == 0) {
                clearstatcache();
            }

            $this->scan(false);

            if ($this->stable != $this->current) {
                $this->cache->set(md5($this->dir), $this->current);
                $this->stable = $this->current;
            }

            sleep($this->timeout);
            $i ++;

            // printf("Memory Usage : %0.4f \n", memory_get_peak_usage(false) /
            // 1024);
        }
    }

    private function scan($new = false) {
        $rdi = new FilesystemIterator($this->dir, FilesystemIterator::SKIP_DOTS);

        $this->current = array();
        foreach($rdi as $file) {

            // Skip files that are not redable
            if (! $file->isReadable())
                return false;

            $path = addslashes($file->getRealPath());
            $keyHash = md5($path);
            $fileHash = $file->isFile() ? md5_file($path) : "#";

            $hash["t"] = $file->getMTime();
            $hash["h"] = $fileHash;
            $hash["f"] = $path;

            $this->current[$keyHash] = json_encode($hash);
        }

        if ($new === false) {
            $this->process();
        }
    }

    public function hook(Callable $call, $type = Monitor::NOTIFY_ALL) {
        $this->hook[$type][] = $call;
    }

    private function process() {
        if (isset($this->hook[self::NOTIFY_NEW])) {
            $diff = array_flip(array_diff(array_keys($this->current), array_keys($this->stable)));
            $this->notify(array_intersect_key($this->current, $diff), self::NOTIFY_NEW);
            unset($diff);
        }

        if (isset($this->hook[self::NOTIFY_DELETED])) {
            $deleted = array_flip(array_diff(array_keys($this->stable), array_keys($this->current)));
            $this->notify(array_intersect_key($this->stable, $deleted), self::NOTIFY_DELETED);
        }

        if (isset($this->hook[self::NOTIFY_MODIFIED])) {
            $this->notify(array_diff_assoc(array_intersect_key($this->stable, $this->current), array_intersect_key($this->current, $this->stable)), self::NOTIFY_MODIFIED);
        }
    }

    private function notify(array $files, $type) {
        if (empty($files))
            return;

        foreach($this->hook as $t => $hooks) {
            if ($t & $type) {
                foreach($hooks as $hook) {
                    foreach($files as $file) {
                        $info = json_decode($file, true);
                        $hook($info['f'], $type);
                    }
                }
            }
        }
    }
}