Корректное использования flock() в PHP. Предотвращение множественного доступа к файлу

Сперва со счетчиками посещений, потом с рейтингом сайтов. Начались проблемы. Самые популярные участники рейтинга дают более 3000 показов счетчика, а значит и обновлений статистики в сутки. Файлы-семафоры немного спасали. Но тормозили мне работу и случалис

Корректное использования flock() в PHP

» » Сообщение:

Мучился с этим где-то около года. Сперва со счетчиками посещений - но это еще как-то не страшно было - осыпалось и осыпалось.. Потом с рейтингом сайтов. Вот тут уже начались проблемы. Самые популярные участники рейтинга дают более 3000 показов счетчика (а значит и обновлений статистики) в сутки. Всего же их более сотни.

Файлы-семафоры немного спасали. Но, во-первых, тормозили мне работу, во-вторых, случались регулярные dead-lock'и.

А решилось все штатным flock()'ом.

Сперва преамбула. Напомню формат использования:

Код: Выделить всё Развернуть
$fh=fopen(..);
flock($fh,2);
 // работаем с файлом
fclose($fh); // flock перед fclose не обязателен.

В идеале работает так, что если открываем незаблокированный файл, то все ок, если заблокированный - то ждем, пока освободится.

Реально что происходит: открываем незаблокированный файл. И тут, в момент, пока уже сработал fopen(), но еще не сработал flock() срабатывает fopen второго скрипта! Все, конфликт доступа. Каждый пишет что-то свое, счетчики слетают.
Это не маловероятное событие!
При ~1000 запусков скрипта в сутки сбои случаются каждые 2-3 дня!

Решение простое.
Файл нужно открывать так, чтобы открытием не убивать его содержимое! Т.е. не по 'w', а по 'a'. А потом уже использовать fseek и ftruncate.
Вот пример простого непадучего счетчика прямо из моей библиотеки. На входе идентификатор счетчика (обычно это URL страницы, если отсутствует, то берется имя текущего скрипта).

Подразумевается, что каталог счетчиков - $DOCUMENT_ROOT/../cgi-bin/data/counts/

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

Код: Выделить всё Развернуть
function count_inc($id="")
{
      global $PHP_SELF, $DOCUMENT_ROOT;
      if(!$id)
            $id=$PHP_SELF;
 
      $idb=str_replace("/","#",substr($id,1));
      $ids=str_replace("/","_",substr($id,1));
      $ids=str_replace("?","_",$ids);
      $ids=str_replace(":","_",$ids);
 
      $cnt_file="$DOCUMENT_ROOT/../cgi-bin/data/counts/$ids.txt";
 
      $fh=fopen($cnt_file,"a+");
      flock($fh,2);
      fseek($fh,0);
      list($sid,$count,$date,$rlast,$clast)=split("\|",chop(fread($fh,9999)));
      $count++;
 
      if(!$date||!$count||!$rlast||!$clast||strftime("%Y",$date)<"2001")
      {
            if(!$ddate||!$dcount||!$drlast||!$dclast)
            {
                  $count=1;
                  $date=time();
                  $rlast=time();
                  $clast=time();
            }
            else
            {
                  $count=$dcount;
                  $date=$ddate;
                  $rlast=$drlast;
                  $clast=$dclast;
            }
      }
 
      ftruncate($fh,0);
      fwrite($fh,"$id|$count|$date|$clast|".time());
      fclose($fh);
      chmod($cnt_file,0666);
}

Т.е. делаем так:

Код: Выделить всё Развернуть
$fh=fopen($file, "a+"); // это если еще и читать надо. Если только писать - то достаточно "a"
flock($fh); // блокируем файл.
fseek($fh,0); // переходим в начало.
fread(..); // читаем, если надо.
// ...
ftruncate($fh,0); // режем/трем файл по нулям.
fwrite(..); // пишем что надо
fclose($fh);

Это работает без сбоев месяцами при тысячах обращений к файлу в день.

Следует также помнить, что читать файл нужно только через fopen() (можно fopen(..","r")) НО НЕ ЧЕРЕЗ file() или readfile(). Может считаться полузаписанный файл.
Т.е. если это только для разового чтения и сбой не страшен, то пожалуйста.
Если же требуется что-то прочитать, изменить и записать, то все надо делать в одно открытие файла, как в приведенном выше примере.

© www.airbase.ru

dead-lock, flock



Похожие темыКомментарии ПросмотрыПоследнее сообщение
0653Лимиты в системе юКоз - Ограничения...
Сообщение от: Admin