file_get_contents & file_put_contents 可靠还是会导致数据丢失?基准测试结果

Is file_get_contents & file_put_contents reliable or can lead to loss of data? Benchmark results

我想知道如果多个脚本共享同一个文件会发生什么。我将测试上传到远程服务器,他们使用 HDD 存储数据。总共有 7 个测试,但 6 个家庭是兼容的。

我有 7 个不同大小的文件上传到服务器和测试。它是从文件中读取和写入数据的循环。

循环中有 50 微秒的延迟。循环重复 50 次。

我测量完成每一圈所需的时间。

测试差异(T):

使用file_get_contents/file_put_contents

T2 - SOURCE <> TARGET - 从原始文件读取数据,将数据写入不同的(新)文件

T3 - SOURCE = TARGET - 1. 将数据从原始文件复制到目标; 2.读取源数据->写入数据; 3. 重复第3点:即我读了我写的数据。本次测试使用同一个文件写入数据。

T4 - SOURCE = TARGET - 我重复了与 T3 中相同的测试,时间变短了。

Using fopen, flock, fread, flock, fclose, fopen, flock, fopen, fwrite, fflush, fclock, fclose ... 这个代码比较复杂,不过我这里已经测试了fflush。我还使用 clearstatcache、stat 和 touch 以及 clearstatcache、filesize。检查有效性。测试 T5 - T7 不如 T2-T4 可靠,因为有时写操作会失败。我测试了文件大小,当它不正确时,我将文件从原始文件复制(恢复)回来。

T5: (fflush) 源=目标

T6: (fflush) 源 <> 目标

T7: (fflush) SOURCE <> TARGET + 我已经从循环中删除了 50 微秒的延迟(似乎 validity/reliability 更糟延迟)。

我从 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 只是按预期显示正常时间 - 文件越大,处理所需的时间就越长。与 HDD 和 4 个脚本 运行 同时出现的预期相当慢。

所以我的问题是:

T3-T4 的结果是否意味着 file_read_contents 和 file_put_contents 对于此类工作不可靠? 在我看来就像他们根本不从文件中读取数据而是从缓冲区复制数据一样,这意味着旧数据被保存,而不是当前数据被并发脚本更改。我会欢迎更多信息。我花了很多时间寻找答案,但没有找到明确的答案。我做了这个测试,因为我需要证据。你想使用我的脚本,但我不确定我是否可以将 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 选项来监控每个进程。注意:触摸可能始终无法正常工作。

注意:这不是测试请求,这只是审核请求。

另外:请不要被黄色曲线混淆。有两种黄色。 T4 黄色在图表上几乎看不到,因为它的值非常低。

我不知道你想做什么,但恐怕你走错了路。 如果您担心碰撞,您应该使用可以解决此类问题并为您提供豪华访问方法的数据库。 PHP 有 5 个不同的数据库供您选择来自.

注意,这两个函数之间没有冲突,都是原子的和可靠的。 问题是如果您读取、修改和保存文件。这三个操作不在一个事务中,因此当您重叠时可能会丢失数据。如果您需要这样的用例,请使用数据库。

缓冲是每个程序员都应该知道的基本文件系统功能。这适用于所有编程语言,而不仅仅是 PHP.

意识到您实际上是在尝试创建数据库引擎,即发明轮子。许多数据库看起来像一个纯文本文件,但它们之上的引擎已经准备就绪并经过测试。为什么不使用这五个中的任何一个?

我想再添加一项测试。这是使用 "directory lock" 制作的。这不是使用 flock,而是创建目录。如果该目录不存在,它会尝试创建一个目录并继续读取和写入数据。注意:这不是完美的解决方案。循环有 50 个循环。没有延迟。但是函数 atomicFuse 有延迟。我post这不是真正的解决方案,而是作为测试和测试结果进行比较。

/*
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 523kB 和 2x 948kB) 来自 FF1 的请求:5 个失败(前 5 个文件 523kB) 来自 Opery 的请求:0 次失败(100% OK) 来自 FF2 的请求:0 次失败(100% 成功)

我将再添加一张图表,不包含测试失败的值。那将是完全不同的。

T8b 的另一个图表,我已经从函数开始的开头删除了非常高的数字。这对平均值的影响很小。