Самый быстрый способ сравнить состояние каталога или хеширование для удовольствия и прибыли - PullRequest
12 голосов
/ 03 декабря 2010

У нас есть приложение PHP, и мы подумали, что было бы полезно узнать, изменилось ли его приложение с момента последнего выполнения. Главным образом из-за управления кешами и тому подобным, а также из-за того, что к нашим приложениям иногда обращаются люди, которые не забывают очистить кеш при изменениях. (Изменение людей - очевидный ответ, но, увы, не очень достижимо)

Мы придумали это, что является самым быстрым, что нам удалось сделать, запустив в среднем 0,08 на машине разработчика для типичного проекта. Мы экспериментировали с shasum, md5 и crc32, и это самое быстрое. Мы в основном md5ing содержимое каждого файла, и md5'ing этого вывода. Безопасность не является проблемой, мы просто заинтересованы в обнаружении изменений файловой системы с помощью другой контрольной суммы.

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

Полагаю, вопрос в том, может ли это быть еще больше оптимизировано?

(я понимаю, что обрезка svn будет стоить, но поиск отнимает у компонентов наименьшее количество времени, поэтому он будет довольно минимальным. Мы проверяем это на рабочей копии atm)

Ответы [ 6 ]

12 голосов
/ 24 мая 2013

Вы можете получать уведомления о модификациях файловой системы, используя расширение inotify .

Может быть установлен с pecl:

pecl install inotify

Или вручную (скачать, phpize && ./configure && make && make install как обычно).

Это необработанная привязка к системным вызовам inotify linux, и, вероятно, это самое быстрое решение для linux.

См. Этот пример простой реализации tail: http://svn.php.net/viewvc/pecl/inotify/trunk/tail.php?revision=262896&view=markup


Если вам нужна библиотека более высокого уровня или поддержка для не Linux-систем, взгляните на Lurker .

Он работает в любой системе и может использовать inotity, когда он доступен.

См. Пример из 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();
6 голосов
/ 25 мая 2013

Вместо того, чтобы переходить к содержимому файла, вы можете использовать ту же технику с именем файла и временными метками:

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

Это намного быстрее, чем чтение и хэширование содержимого каждого файла.

5 голосов
/ 03 декабря 2010

Вместо активного поиска изменений, почему бы не получать уведомления, когда что-то меняется. Взгляните на PHP FAM - API монитора изменений файлов

FAM отслеживает файлы и каталоги, уведомляя заинтересованные приложения об изменениях. Дополнительную информацию о FAM можно получить по адресу »http://oss.sgi.com/projects/fam/.. Сценарий PHP может указывать список файлов, которые FAM должен отслеживать, используя функции, предоставляемые этим расширением. Процесс FAM запускается при открытии первого подключения любого приложения к нему. Он выходит после того, как все подключения к нему были закрыты.

CAVEAT: требуется дополнительный демон на машине, и расширение PECL не поддерживается.

4 голосов
/ 11 марта 2011

Мы не хотели использовать FAM, поскольку нам нужно было бы установить его на сервер, а это не всегда возможно.Иногда клиенты настаивают на том, что мы используем их поврежденную инфраструктуру.Поскольку оно прекращено, трудно получить одобренные изменения и в виде красной ленты.

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

После этого приложение использовало функцию, чтобы проверять, были ли изменения, чтобы очистить кеш, если они были.Поскольку мы на самом деле не хотим останавливать приложение во время его выполнения, такого рода вещи лучше всего тщательно обрабатывать как асинхронный процесс с использованием fsockopen.Это дает лучший «прирост скорости» в целом, просто будьте осторожны с условиями гонки.

Отметьте это как «ответ» и проголосуйте против FAM.

2 голосов
/ 29 мая 2013

Определенно, что вы должны использовать, это Inotify это быстрая и простая в настройке, несколько вариантов непосредственно от bash или php посвящать простой node-inotify экземпляр для этой задачи

Но Inotify не используется в Windows, но вы можете легко написать приложение командной строки с помощью FileSystemWatcher или FindFirstChangeNotification и позвоните по номеру 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);
                    }
                }
            }
        }
    }
}
2 голосов
/ 29 мая 2013

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

, что сказано, вам не нужно изобретать колесо.

вы могли бы ускорить процесс, только просматривая метаданные, считанные из индексов каталога (метка времени изменения, размер файла и т. Д.)

Вы также можете остановиться, как только заметите разницу (теоретически следует сократить время наполовину в среднем) и т. Д. Есть много.

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

У инструмента linux diff есть опция -q (быстрая).

вам также понадобится использовать его с рекурсивным параметром -r.

diff -r -q dir1/ dir2/

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...