Php 比较目录状态的最快方法,或者为了乐趣和利润而进行哈希

Php 比较目录状态的最快方法,或者为了乐趣和利润而进行哈希,php,linux,bash,hash,Php,Linux,Bash,Hash,我们有一个PHP应用程序,我们认为让应用程序知道自上次执行以来其组成是否发生了变化可能是有利的。主要是因为管理缓存等,并且知道我们的应用程序有时会被不记得在更改时清除缓存的人访问。(改变人民是显而易见的答案,但遗憾的是,这并不是真正可以实现的) 我们已经想出了这个,这是我们设法弥补的最快的,在一个典型项目的开发人员机器上平均运行0.08。我们已经试验过shasum、md5和crc32,这是最快的。我们基本上是对每个文件的内容进行md5处理,并对输出进行md5处理。安全性不是一个问题,我们只关心通

我们有一个PHP应用程序,我们认为让应用程序知道自上次执行以来其组成是否发生了变化可能是有利的。主要是因为管理缓存等,并且知道我们的应用程序有时会被不记得在更改时清除缓存的人访问。(改变人民是显而易见的答案,但遗憾的是,这并不是真正可以实现的)

我们已经想出了这个,这是我们设法弥补的最快的,在一个典型项目的开发人员机器上平均运行0.08。我们已经试验过shasum、md5和crc32,这是最快的。我们基本上是对每个文件的内容进行md5处理,并对输出进行md5处理。安全性不是一个问题,我们只关心通过不同的校验和检测文件系统的变化

time (find application/ -path '*/.svn' -prune -o -type f -print0 | xargs -0 md5 | md5)
我想问题是,这能进一步优化吗


(我意识到修剪svn会有成本,但find会占用组件最少的时间,因此它将非常小。我们正在工作副本atm上测试这一点)

与其积极搜索更改,为什么不在某些更改时得到通知呢。看看

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


警告:需要在机器上安装一个额外的守护程序,并且PECL扩展未维护。

我们不想使用FAM,因为我们需要在服务器上安装它,而这并不总是可能的。有时,客户坚持要求我们在其损坏的基础设施上部署。因为它已经停产了,所以很难在繁文缛节上批准这一改变

提高原始版本速度的唯一方法是确保文件列表尽可能简洁。IE只对那些真正重要的目录/文件进行哈希处理。删除不相关的目录可以大大提高速度

在此之后,应用程序使用该函数检查是否有更改,以便在有更改时执行缓存清除。由于我们不想在应用程序执行此操作时停止应用程序,因此最好使用fsockopen将这类事情作为异步进程仔细分配。总的来说,这会带来最好的“速度提升”,只是要注意比赛条件


将此标记为“答案”,并向上投票FAM答案。

您可以使用

它可以与pecl一起安装:

pecl install inotify
或者手动(下载,phpize&&&./configure&&make&&makeinstall如常)

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

请参见此简单的
tail
实现示例:


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

它可以在任何系统上工作,并且可以在inotity可用时使用它

请参见下面的示例:


您可以对文件名和时间戳使用相同的技术,而不是按文件内容:

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

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

既然你有svn,为什么不按修订版来做呢。我意识到您正在跳过svn文件夹,但我想您这样做是为了速度优势,并且您的生产服务器中没有修改过的文件

比因说,你不必重新发明轮子

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

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

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

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

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

diff-r-q dir1/dir2/


它使用了大量的优化,我严重怀疑您是否能够在不花费大量精力的情况下对其进行显著改进。

您肯定应该使用的是它的快速、易于配置、多个选项,这些选项可以直接从一个简单的实例获得,也可以专门用于此任务

但是
Inotify
不适用于windows,但您可以使用或轻松编写命令行应用程序,并通过
exec

如果您只想寻找
PHP解决方案
,那么这相当困难,您可能无法获得所需的性能,因为唯一的方法是连续扫描该文件夹

这是一个简单的实验

  • 不要在生产中使用
  • 无法管理大型文件集
  • 不支持文件监视
  • 仅支持新建、删除和修改
  • 不支持递归
范例

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);
                    }
                }
            }
        }
    }
}

+1这很好-类似于“make”逻辑,并且可能已经足够了,除非您担心故意“黑客”缓存内容以某种方式破坏程序。既然您有svn,为什么不按修订版进行操作呢。我意识到您正在跳过svn文件夹,但我想您这样做是为了速度优势,并且您的生产服务器中没有修改过的文件。。。
// 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);
                    }
                }
            }
        }
    }
}