Является ли file_get_contents & file_put_contents надежным или может привести к потере данных? Результаты тестов - PullRequest
0 голосов
/ 12 октября 2019

Мне было интересно, что произойдет, если несколько скриптов совместно используют один и тот же файл. Я загрузил тест на удаленный сервер, где они используют жесткий диск для хранения данных. Всего было проведено 7 тестов, но семейство из 6 совместимо.

У меня есть 7 файлов разного размера, которые я загрузил на сервер и тест. Это цикл, который читает и записывает данные из файлов.

В цикле задержка составляет 50 микросекунд. Цикл повторяется 50x.

Я измеряю время, необходимое для выполнения каждого круга.

Различия в тестах (T):

Использование file_get_contents / file_put_contents

T2 - ИСТОЧНИК <> TARGET - считывает данные из исходного файла,записывает данные в другой (новый) файл

T3 - SOURCE = TARGET - 1. копирует данные из исходного файла в целевой;2. читает исходные данные -> записывает данные;3. пункт 3 повторяется: т.е. я читаю данные, которые я написал. Этот тест использует тот же файл для записи данных.

T4 - SOURCE = TARGET - я повторил тот же тест, что и в T3, получая закороченные времена.

Использование fopen, flock, fread, flock, fclose, fopen, flock, fopen, fwrite, fflush , fclock, fclose ... Это сложный код, но здесь я протестировалfflush. Я также использую clearstatcache, stat и touch и clearstatcache, размер файла. Проверить правильность. Тесты T5 - T7 были менее надежными, чем T2-T4, потому что иногда операция записи не выполнялась. Я проверил размер файла и, когда он был неправильным, я скопировал (восстановил) файл обратно из исходного файла.

T5 : (fflush) SOURCE = TARGET

T6 : (fflush) SOURCE <> TARGET

T7 : (fflush) SOURCE <> TARGET + Я удалил задержку в 50 микросекунд из цикла (кажетсянапример, достоверность / надежность хуже при наличии задержки).

Я сделал 4 запроса от 4 разных браузеров - поэтому в каждом тесте было 4 набора данных (всего 7 * 50 * 4 значений).

Теперь я собрал все данные, создал таблицы и диаграммы. Это одна из многих диаграмм, показывающая минимальные и максимальные значения среднего значения.

T4 желтого цвета и T3 зеленого обеспечивает очень малое время, поэтому они подозрительны. Например, средние значения времени T4: 0,001

0.001 0.002 0.003 0.002 0.004 0.003 0.004 0.001 0.004 0.001 0.004 0.001 0.004

и времена T3:

0.002 0.003 0.001 0.001 0.003 0.003 0.006 0.007 0.002 0.003 0.004 0.004 0.019 0.019

Значения T2 кажутся нормальными, но это можно объяснить тем фактом, что это былочтение из другого файла, чем было записано.

T5-T7 просто показывает нормальное время, как и ожидалось - чем больше файл, тем больше время, необходимое для обработки. Довольно медленный, как и ожидалось, от жесткого диска и 4-х сценариев, запущенных одновременно.

Итак, мой вопрос здесь:

Означают ли результаты T3-T4, что file_read_contents и file_put_contentsне являются надежными для этого типа работы? Мне кажется, что они просто не читают данные из файла, но копируются из буфера, что означает, что старые данные сохранены, а не текущие данные были изменены с помощью concurentскрипт. Я хотел бы получить больше информации. Я потратил много времени на поиск ответов, но не нашел четкого ответа. Я сделал это тесты, потому что мне нужны доказательства. Вы хотите использовать мои сценарии, но я не уверен, могу ли я вставить здесь 6 сценариев? Теперь я добавлю только тест fflush № 7, который наиболее полезен.

<?PHP 
clearstatcache();
$_DEBUG_ = false;

echo "Lock and flush tester.".time()."<br>";
die;

while ( time()<1570787996 )
 {
 usleep(500);
 }


function test($n, $p, $_DEBUG_){
  $sname = "$n";    // source
  $tname = "$n.txt";// target
  echo "<h4>$n at ".time()."</h4>";
  for ($i = 0; $i<50; $i++ ){
    $start = microtime(true);
    clearstatcache(); // needed for filesize and touch    
    $st = stat("$sname");
    $original_size = $st['size'];
    if ( $_DEBUG_ )
      echo "; 1) prevAccess by ".$st['mtime']." fsize ".$st['size']."; ";
    $fsize = filesize($sname);
    if ( $original_size <> $fsize )
      die("; fsize total FAILTURE; ");
    if ($fsize === 0)
     echo "! <b>The fsize is 0</b>: stat(): ".$st['size']." ;";    
    else
      {
      // READ OPERATION AND LOCK FOR SHARE
       $locked = false;     
       for ($c = 0; !$locked; $c++):      
         if ( $c > 400)
           break;
         $fp = fopen($sname, "r");
         $locked = flock($fp, LOCK_SH);
         if ($locked)
           break;
         else
           {
           echo "failed to get LOCK_SH;<br>";
           usleep(5000);
           }
       endfor;
       $s = fread($fp, $fsize );
       $success = flock($fp, LOCK_UN);
       if ( $success === false  )
         die("; r flock release failed; ");
       $success = fclose($fp);
       if ( $success === false  )
         die("; fclose failed; ");
       // 10 - data loaded , $p - browser
       if ( $success )
         { 
         $result = touch("$sname",strlen($s),$p);
         if ( $_DEBUG_ )
            echo "; TOUCH: $result;";
         }
       else
         die("fclose FAIL.");
       if ( strlen($s)<60 ) 
          echo "*$s LENGTH:".strlen($s)."<br>";
      }
    clearstatcache();
    $st = stat("$tname");                               
    if ( $_DEBUG_ )
      echo "; 2) prevAccess by ".$st['mtime']." fsize is ".$fsize."; ";

    // WRITE OPERATION WITH LOC_EX
    $fp = fopen($tname, "w");
    $locked = false; 
    $locked = flock($fp, LOCK_EX);
    if ( $locked ) {  // acquire an exclusive lock
        $success = fwrite($fp, $s);
        if ( $success === false)
          echo "; w FAILED;";
        else
          if ( $_DEBUG_ )
                echo " $success B written; ";
        $success = fflush($fp);// flush output before releasing the lock
        if ( $success === false ) 
          echo "; flush FAILED; ";
        $success = flock($fp, LOCK_UN);    // release the lock
        if ( $success === false ) 
          echo "; release FAILED; ";
        $success = fclose($fp);
        if ( $success === false ) 
          echo "; fclose FAILED; ";
        clearstatcache(); // needed for filesize and touch
        $fsize = filesize($tname);
        if ($original_size>$fsize)
            {
            echo "; <b>WRITE FAILED, restoring</b>;";
            $original_fname = "$n";
            $result = copy($original_fname, $tname);
            if ($result == false )
              die(" <b>TOTAL FAILTURE: copy failed.</b>");
            else
              echo " <b>RESTORED</b>;";
            }
        else
        {
          if ($fsize === 0)
           echo "! THE FILE WAS NOT WRITTEN: data length: ".strlen($s)." fsize: $fsize RESOURCE: $fp<br>";    
          if ( $success ) 
              touch("$tname",$fsize,$p);
        }
    } else {
        echo "Couldn't get the lock!";
    }
     $time_elapsed_secs = microtime(true) - $start;
     if ( $time_elapsed_secs === 0 )
       echo " FAILED ";
    echo "time: $time_elapsed_secs s<br>"; 
  }
}

switch ( $_SERVER['HTTP_USER_AGENT'] ):
  // FF 1:
  case "Mozilla/5.0 (Windows NT 5.1; rv:49.0) Gecko/20100101 Firefox/49.0": 
    $p = 1; break;
  // Chrome:
  case "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36":
    $p = 2; break;
  // OPERA:
  case "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36 OPR/36.0.2130.80":  
    $p = 3; break;
endswitch;

copy("523","523.txt");
copy("948","948.txt");
copy("1371","1371.txt");
copy("1913","1913.txt");
copy("2701","2701.txt");
copy("4495","4495.txt");
copy("6758","6758.txt");

test("523",$p,$_DEBUG_);
test("948",$p,$_DEBUG_);
test("1371",$p,$_DEBUG_);
test("1913",$p,$_DEBUG_);
test("2701",$p,$_DEBUG_);
test("4495",$p,$_DEBUG_);
test("6758",$p,$_DEBUG_);
die;
echo "php: " . phpversion();
?>
<?PHP echo "php: " . phpinfo();
?>

Вы можете включить опцию $ DEBUG для мониторинга каждого процесса. Примечание. Сенсорный экран может работать не всегда корректно.

Примечание. Это не запрос на проверку, это просто запрос на проверку.

file_put_contents vs fflush

Также: пожалуйста, не смущайтесь желтой кривой цвета. Есть два желтых цвета. Желтый T4 почти не виден на диаграмме, потому что он имеет очень низкие значения.

Ответы [ 2 ]

2 голосов
/ 12 октября 2019

Я не знаю, что вы пытаетесь сделать, но я боюсь, что вы пошли не в ту сторону. Если вас беспокоит коллизия, вам следует использовать базу данных, которая позаботится о таких проблемах и предложит вам роскошные методы доступа. PHP поставляется с 5 различными базами данных, из которых вы можете выбирать.

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

Буферизация - это базовая функция файловой системы, которую должен программистзнать. Это относится ко всем языкам программирования, а не только к PHP.

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

0 голосов
/ 13 октября 2019

Я хотел бы добавить еще один тест. Этот был сделан с использованием "блокировки каталога". Вместо использования flock, это создает каталог. Если каталог не существует, он пытается создать его и продолжает читать и записывать данные. Обратите внимание: это не идеальное решение. Цикл имеет 50 циклов. Без задержки. Но функция atomicFuse имеет задержку. Я публикую это не как реальное решение, а просто как тест и результат теста на сравнение.

/*
n is file size in kB
c is counter for optimalization
first call must have c = 0;
*/
function atomicFuse($n, $c, $disableDelay = false){
  $start = false;
  if ( !file_exists("$n.t") ) 
   $start = mkdir("$n.t");
  if ( !$disableDelay ){
    if ( $start == false )
     {
     $n = $n*30;
     switch($c):      // Delay example increase:
       case 0: break; // 0,01569 total
       case 1: break; // 0,03138 total
       case 2: $n = $n*2; break; // 0,06276 total
       case 3: $n = $n*4; break; // 0,12552 total
       // case 4: You need at least *6 or *8 to get out of problems with extrem times
       case 4: $n = $n*8; break; // 0,25104 t.(upper limit)
       // In case of heavy traffic:
       case 5: $n = $n*8; break; // 0,36087 total extrem
       case 6: $n = $n*10; break; // 0,51777 total extrem
       case 7: $n = $n*20; break; // 1,03554 total extrem
       default: $n = $n*8; break;
     endswitch;
     usleep($n);
     echo ($n)."<br>";
     }
    }
  return $start;
}

Реализация atomicFuse:

  for ($i = 0; $i<50; $i++ ){
    $start_time = microtime(true);
      {
      $start = atomicFuse($n,0);
      if (!$start) $start = atomicFuse($n,1);
      if (!$start) $start = atomicFuse($n,2);
      if (!$start) $start = atomicFuse($n,3);
      if (!$start) $start = atomicFuse($n,4);
      if (!$start) $start = atomicFuse($n,5);
      if (!$start) $start = atomicFuse($n,6);
      if (!$start) $start = atomicFuse($n,7);
      if (!$start) $start = atomicFuse($n, false);
      if (!$start) echo "<b>Atomicity failed.</b> ";
      if ( $start )
         {
         // do some action
         $success = rmdir("$n.t"); // remove atomic fuse
         }
      } 
    }

Минус T8 результатов,максимальное среднее значение:

0.006 0.083 0.018 0.156 0.072 0.182 0.100 0.255 0.168 0.276 0.224 0.383 0.224 0.406

Важное замечание: Этот тест очень специфичен. У него есть некоторые атомарные сбои, поэтому в начале некоторого раздела есть большие задержки.

Таким образом, каждый запрос, сделанный конкретным браузером на моем ПК, приводил к следующим ошибкам: запрос из Chrome: 6 не удался (4x 523 КБ и 2 x 948 КБ)) запрос от FF1: 5 не выполнен (первые 5 файлов 523 КБ), запрос от Opery: 0 не выполнен (100% ОК), запрос от FF2: 0 не выполнен (100% ОК)

T8 test - black line

Я добавлю еще одну диаграмму без значений, при которых тест не пройден. Это будет совсем по-другому.

Еще одна диаграмма с T8b, я убрал очень большие числа в начале запуска функции. Это немного меняет среднее значение.

The high numbers changed down

...