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

Я создал сценарий для использования с моим веб-сайтом, который должен удалять самую старую запись в кэше, когда новый элемент необходимо кэшировать. Мой веб-сайт очень большой, на нем 500000 фотографий, а объем кеша составляет 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, поэтому весь файл в системе будет:

/cacherootfolder/abc/def

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

/cacherootfolder/111/222

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

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

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

1 ответ

Решение

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

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

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

Так что ваши truncate избыточен, как и ваш rewind,

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

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

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

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

Другие вопросы по тегам