Apache / PHP использует 100% CPU при попытке освободить место в кеше

1

Я создал сценарий для использования с моим веб-сайтом, который должен стереть самую старую запись в кеше, когда нужно будет кэшировать новый элемент. Мой сайт очень большой, на нем 500 000 фотографий, а пространство для кеша установлено на 2 ГБ.

Эти функции вызывают проблемы:

function cache_tofile($fullf, $c)
{
    error_reporting(0);
    if(strpos($fullf, "/") === FALSE)
    {
        $fullf = "./".$fullf;
    }
    $lp = strrpos($fullf, "/");
    $fp = substr($fullf, $lp + 1);
    $dp = substr($fullf, 0, $lp);
    $sz = strlen($c);
    cache_space_make($sz);
    mkdir($dp, 0755, true);
    cache_space_make($sz);
    if(!file_exists($fullf))
    {
        $h = @fopen($fullf, "w");
        if(flock($h, LOCK_EX))
        {
            ftruncate($h, 0);
            rewind($h);
            $tmo = 1000;
            $cc = 1;
            $i = fputs($h, $c);
            while($i < strlen($c) || $tmo-- > 1)
            {
                $c = substr($c, $i);
                $i = fwrite($h, $c);
            }
            flock($h, LOCK_UN);
            fclose($h);
        }
    }
    error_reporting(7);
}

function cache_space_make($sz)
{
    $ct = 0;
    $cf = cachefolder();
    clearstatcache();
    $fi = shell_exec("df -i ".$cf." | tail -1 | awk -F\" \" '{print \$4}'");
    if($fi < 1)
    {
        return;
    }
    if(($old = disk_free_space($cf)) === false)
    {
        return;
    }
    while($old < $sz)
    {
        $ct++;
        if($ct > 10000)
        {
            error_log("Deleted over 10,000 files. Is disk screwed up?");
            break;
        }
        $fi = shell_exec("rm \$(find ".$cf."cache -type f -printf '%T+ %p\n' | sort | head -1 | awk -F\" \" '{print \$2}');");
        clearstatcache();
        $old = disk_free_space($cf);
    }
}

cachefolder() - это функция, которая возвращает правильное имя папки с / добавлением к ней.

Когда функции выполняются, загрузка процессора для apache составляет от 95% до 100%, а другие службы на сервере крайне медленны для доступа в течение этого времени. Я также заметил в whm, что использование кеш-диска составляет 100% и отказывается отбрасываться до тех пор, пока я не очищу кеш. Я ожидал больше, чем, может быть, 90%.

То, что я пытаюсь сделать с помощью функции cache_tofile, - это попытка освободить место на диске, чтобы создать папку, а затем свободное место на диске, чтобы сделать файл кеша. Функция cache_space_make принимает один параметр, представляющий объем свободного места на диске.

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

Формат файла кеша выглядит следующим образом:

/cacherootfolder/requestedurl

Например, если вы запрашиваете http://www.example.com/abc/def, то из обеих функций папка, которая должна быть создана, - это abc, а затем файл def def, поэтому весь файл в системе будет:

/cacherootfolder/abc/def

Если запрашивается http://www.example.com/111/222, тогда создается папка 111 и будет создан файл 222

/cacherootfolder/111/222

Каждый файл в обоих случаях содержит то же содержимое, что и запросы пользователей, основанные на URL-адресе. (пример: /cacherootfolder/111/222 содержит тот же контент, что и при просмотре источника с http://www.example.com/111/222)

Целью системы кеширования является доставка всех веб-страниц с оптимальной скоростью.

Тогда мой вопрос заключается в том, как я могу предотвратить попытку системы блокироваться, когда кеш заполнен. Есть ли лучший код, который я могу использовать, чем то, что я предоставил?

Теги:
caching
cpu-usage

1 ответ

1
Лучший ответ

Я бы начал с замены || в вашем коде &&, что, скорее всего, было намерением.
В настоящее время цикл всегда будет работать не менее 1000 раз - я очень надеюсь, что намерение состояло в том, чтобы прекратить попытки после 1000 раз.

Кроме того, ftruncate и rewind.
Из руководства PHP по fopen (выделение мое):

'w' Открыт только для записи; поместите указатель файла в начало файла и обрезайте
файл до нулевой длины. Если файл не существует, попытайтесь его создать.

Итак, ваш truncate избыток, как и ваша rewind.

Затем просмотрите файл shell_exec.
Тот, который находится вне цикла, не кажется слишком узким местом для меня, но внутри цикла...
Скажем, у вас есть 1'000'000 файлов в этой папке кеша.
find будет счастливо перечислить все из них для вас, независимо от того, сколько времени потребуется.
Затем вы сортируете этот список.
И затем вы сбросите 999'999 записей этого списка в туалет, и только сохраните первый.
Затем вы делаете некоторые вещи с awk которые мне действительно не нравятся, и затем вы удаляете файл.
На следующей итерации вам нужно будет пройти только 999'999 файлов, из которых вы отбрасываете только 999'998.
Смотрите, куда я иду?
В любом случае, я считаю, что скрипты оболочки из чистой удобной плохой практики, но если вы это сделаете, сделайте это как можно более эффективно, по крайней мере!
Сделайте один shell_exec без shell_exec head -1, сохраните полученный список в переменной и повторите его.
Хотя было бы лучше вообще отказаться от shell_exec и вместо этого запрограммировать соответствующие подпрограммы в PHP (можно утверждать, что find и rm являются машинным кодом и, следовательно, быстрее, чем код, написанный на PHP, для выполнения одной и той же задачи, но там очень много накладные расходы для всего перенаправления IO).

Пожалуйста, сделайте все это, а затем посмотрите, насколько он плохо работает.
Если результаты все еще неприемлемы, я предлагаю вам ввести код, чтобы определить время, необходимое определенным частям этих функций (tip: microtime(true)) или использовать профайлер, например XDebug, чтобы узнать, где именно проводится большая часть вашего времени,

Кроме того, почему вы отключили отчет об ошибках для этого блока? Выглядит более чем подозрительно для меня.

И в качестве небольшого бонуса вы можете избавиться от $cc так как вы его нигде не используете.

  • 0
    Я использовал || потому что это цикл, чтобы убедиться, что данные записаны, потому что иногда fwrite может не возвращать все записанные байты. У меня проблема в том, что этот код может быть выполнен несколькими пользователями одновременно, и если я не очищаю кэш всегда, тогда может быть удален неправильный файл? Возможно, мне нужна специальная функция, чтобы сообщить системе, что этот код может быть выполнен только одним пользователем.
  • 0
    Но ... || все еще означает «по крайней мере 1000 раз» ... и да, вам очень нужно убедиться, что скрипт запускается только по одному за раз. Такой скрипт должен запускаться как cronjob, а не как HTTP-запрос, просто чтобы убедиться, что максимальное время выполнения не затягивает.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню