【问题标题】:file_get_contents getting wrong resultsfile_get_contents 得到错误的结果
【发布时间】:2020-03-01 20:57:30
【问题描述】:

更新

我解决了问题并发布了答案。但是,我的解决方案并不是 100% 理想的。我宁愿只用clearstatcache(true, $target)clearstatcache(true, $link)cache 中删除symlink,但这不起作用。

我也宁愿首先阻止符号链接的缓存,或者在生成符号链接后立即从缓存中删除它。不幸的是,我没有运气。由于某种原因clearstatcache(true) 创建符号链接后不起作用,它仍然被缓存。

我很乐意将赏金奖励给任何可以改进我的answer 并解决这些问题的人。

编辑

我试图通过在每次运行 clearstatcache 时生成一个文件来优化我的代码,这样我只需要为每个符号链接清除一次缓存。出于某种原因,这不起作用。每次symlink 包含在路径中时都需要调用clearstatcache,但为什么呢?一定有办法优化我的解决方案。


我正在使用 PHP 7.3.5nginx/1.16.0。有时file_get_contents 在使用symlink 时会返回错误的值。问题是在删除并重新创建符号链接后,其旧值仍保留在缓存中。有时返回正确的值,有时返回旧值。它看起来是随机的。

我已尝试清除缓存或阻止缓存:

function symlink1($target, $link)
{
    realpath_cache_size(0);
    symlink($target, $link);
    //clearstatcache(true);
}

我真的不想禁用缓存,但我仍然需要 file_get_contents 的 100% 准确度。

编辑

我无法发布我的源代码,因为它太长且太复杂,所以我创建了一个最小的、可重现的示例 (index.php) 来重现问题:

<h1>Symlink Problem</h1>
<?php
    $dir = getcwd();
    if (isset($_POST['clear-all']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        foreach ($nos as $no)
        {
            unlink($dir.'/nos/'.$no.'/id.txt');
            rmdir($dir.'/nos/'.$no);
        }
        foreach (array_values(array_diff(scandir($dir.'/ids'), array('..', '.'))) as $id)
            unlink($dir.'/ids/'.$id);
    }
    if (!is_dir($dir.'/nos'))
        mkdir($dir.'/nos');
    if (!is_dir($dir.'/ids'))
        mkdir($dir.'/ids');
    if (isset($_POST['submit']) && !empty($_POST['id']) && ctype_digit($_POST['insert-after']) && ctype_alnum($_POST['id']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        $total = count($nos);
        if ($total <= 100)
        {
            for ($i = $total; $i >= $_POST['insert-after']; $i--)
            {
                $id = file_get_contents($dir.'/nos/'.$i.'/id.txt');
                unlink($dir.'/ids/'.$id);
                symlink($dir.'/nos/'.($i + 1), $dir.'/ids/'.$id);
                rename($dir.'/nos/'.$i, $dir.'/nos/'.($i + 1));
            }
            echo '<br>';
            mkdir($dir.'/nos/'.$_POST['insert-after']);
            file_put_contents($dir.'/nos/'.$_POST['insert-after'].'/id.txt', $_POST['id']);
            symlink($dir.'/nos/'.$_POST['insert-after'], $dir.'/ids/'.$_POST['id']);
        }
    }
    $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
    $total = count($nos) + 1;
    echo '<h2>Ids from nos directory</h2>';
    foreach ($nos as $no)
    {
        echo ($no + 1).':'.file_get_contents("$dir/nos/$no/id.txt").'<br>';
    }
    echo '<h2>Ids from using symlinks</h2>';
    $ids = array_values(array_diff(scandir($dir.'/ids'), array('..', '.')));
    if (count($ids) > 0)
    {
        $success = true;
        foreach ($ids as $id)
        {
            $id1 = file_get_contents("$dir/ids/$id/id.txt");
            echo $id.':'.$id1.'<br>';
            if ($id !== $id1)
                $success = false;
        }
        if ($success)
            echo '<b><font color="blue">Success!</font></b><br>';
        else
            echo '<b><font color="red">Failure!</font></b><br>';
    }
?>
<br>
<h2>Insert ID after</h2>
<form method="post" action="/">
    <select name="insert-after">
        <?php
            for ($i = 0; $i < $total; $i++)
                echo '<option value="'.$i.'">'.$i.'</option>';
        ?>
    </select>
    <input type="text" placeholder="ID" name="id"><br>
    <input type="submit" name="submit" value="Insert"><br>
</form>
<h2>Clear all</h2>
<form method="post" action="/">
    <input type="submit" name="clear-all" value="Clear All"><br>
</form>
<script>
    if (window.history.replaceState)
    {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

Nginx 配置似乎很可能有问题。没有这些行可能会导致问题:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

这是我的Nginx 配置(你可以看到我已经包含了以上几行):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.websemantica.co.uk;
    root "/path/to/site/root";
    index index.php;

    location / {
        try_files $uri $uri/ $uri.php$is_args$query_string;
    }

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_param   QUERY_STRING            $query_string;
        fastcgi_param   REQUEST_METHOD          $request_method;
        fastcgi_param   CONTENT_TYPE            $content_type;
        fastcgi_param   CONTENT_LENGTH          $content_length;

        fastcgi_param   SCRIPT_FILENAME         $realpath_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
        fastcgi_param   PATH_INFO               $fastcgi_path_info;
        fastcgi_param   PATH_TRANSLATED         $realpath_root$fastcgi_path_info;
        fastcgi_param   REQUEST_URI             $request_uri;
        fastcgi_param   DOCUMENT_URI            $document_uri;
        fastcgi_param   DOCUMENT_ROOT           $realpath_root;
        fastcgi_param   SERVER_PROTOCOL         $server_protocol;

        fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
        fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

        fastcgi_param   REMOTE_ADDR             $remote_addr;
        fastcgi_param   REMOTE_PORT             $remote_port;
        fastcgi_param   SERVER_ADDR             $server_addr;
        fastcgi_param   SERVER_PORT             $server_port;
        fastcgi_param   SERVER_NAME             $server_name;

        fastcgi_param   HTTPS                   $https;

        # PHP only, required if PHP was built with --enable-force-cgi-redirect
        fastcgi_param   REDIRECT_STATUS         200;

        fastcgi_index index.php;
        fastcgi_read_timeout 3000;
    }

    if ($request_uri ~ (?i)^/([^?]*)\.php($|\?)) {
        return 301 /$1$is_args$args;
    }
    rewrite ^/index$ / permanent;
    rewrite ^/(.*)/$ /$1 permanent;
}

目前我在https://www.websemantica.co.uk 有上面的例子。

尝试在表单中添加一些值。它应该每次都以蓝色显示Success!。有时会以红色显示Failure!。从Success! 更改为Failure! 可能需要多次页面刷新,反之亦然。最终,它每次都会显示Success!,因此肯定存在某种缓存问题。

【问题讨论】:

  • 我正在查看相同的案例,并在realpath function page 上发现了非常有用的评论。也许它可以帮助你。
  • @marv255 我尝试将realpathfile_get_conents 一起使用,但没有运气。它有时仍会从缓存中加载。
  • 我的意思不仅是realpath,还包括clearstatcache(true); file_get_conents(realpath($fileName));之类的东西
  • 尝试linux.die.net/man/8/updatedb 在连续调用之间运行命令。虽然我不确定如果是这种情况如何解决 php 中的问题。

标签: php nginx caching file-get-contents symlink


【解决方案1】:

这太过依赖于操作系统级别。那么如何尝试开箱即用。尝试通过readlink读取文件的真实位置,并使用该真实位置路径如何?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);

【讨论】:

  • 我认为这还不够(开箱即用),毕竟 readlink 还依赖于操作系统级别的调用,并且受缓存的影响。
  • 刚刚在 PHP 中测试过:$link = readlink('info.json'); 链接保持不变(缓存)。然而,在下一行读取的内容更改了$data = json_decode(file_get_contents('info.json'))); - 每 3 秒通过 Ajax 请求 - 大约 15x =>(总共 45 秒)内容在旧文件和新文件之间切换。返回的 $link (错误地)相同。
【解决方案2】:

这是 PHP 所需的行为,您可以看到 here,因为 PHP 使用 realpath_cache 来存储由于 performance enhancements 而导致的文件路径,以便它可以减少磁盘操作。

为了避免这种行为,也许你可以在使用get_file_contents函数之前尝试清除realpath_cache

你可以试试这样的:


clearstatcache();
$data = file_get_contents("Your File");

您可以在 PHP 文档上阅读更多关于 clearstatcache 的信息。

【讨论】:

    【解决方案3】:

    有两个缓存。

    首先是操作系统缓存,然后是 PHP 缓存。

    在大多数情况下,clearstatcache(true)file_get_contents(...) 之前完成这项工作。

    但有时您还需要清除操作系统缓存。在 Linux 的情况下,我可以想到两个需要清除的地方。 PageCache (1) 和 dentries/inode (2)。

    这两个都清除了:

    shell_exec('echo 3 > /proc/sys/vm/drop_caches')
    

    注意:这有利于故障排除,但不适用于生产中的频繁调用,因为它会清除整个操作系统缓存并使系统花费一些时间重新填充缓存。

    【讨论】:

    • 这不起作用,它有时仍然会加载缓存的值,我需要一个适合生产中频繁调用的解决方案。
    • @DanBray,你能记录下事情以了解更多关于sometimes的性质吗?
    • @DanBray,您如何检测旧值的外观?会不会是你的测试由于其他测试条件而返回旧值,而那里的值确实发生了变化?
    【解决方案4】:

    “问题出在删除并重新创建符号链接之后”

    如何删除符号链接?删除文件(或符号链接)应该会自动清除缓存。

    否则,你会看到如果你这样做会发生什么:

    // This has "race condition" written all around it
    unlink($link);
    touch($link);
    unlink($link); // Remove the empty file
    symlink($target, $link);
    

    如果这不能解决问题,会不会是像this issue那样是nginx的问题?

    尝试将所有操作记录到日志文件中,看看实际发生了什么

    或者也许……

    ...你能完全不使用符号链接吗?例如,将“文件名”和“实际符号链接目标”之间的映射存储在数据库、内存缓存、SQLite 文件甚至 JSON 文件中。使用例如redis 或其他密钥库,您可以将“文件名”与真正的符号链接目标相关联,并完全绕过操作系统解析。

    根据用例,这甚至可能比使用符号链接更快。

    【讨论】:

    • 我看不出这可能与 nginx 有什么关系,因为 php 进程和本地文件系统之间似乎没有 http 的东西。作为父进程是否使 nginx 具有某种相关性?
    • @BahramArdalan 事实是,我们不知道如何诊断出问题或符号链接是什么或如何使用它们。因此可以想象,内容不匹配是从 nginx 下游检测到的,实际上可能与 PHP 无关。 SCCCE 会很有帮助。
    • 是的。我们必须深入研究“如何”的事情。
    【解决方案5】:

    有两个问题导致了问题。

    第一期

    我已经发布并在问题中进行了编辑。是 Nginx 配置的问题。

    这些行:

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $document_root;
    

    需要替换为:

    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $realpath_root;
    

    第二期

    第二个问题是我需要在调用file_get_contents 之前先调用clearstatcache。我只想在绝对必要的时候调用clearstatcache,所以我写了一个函数,只在目录包含symlink时清除缓存。

    function file_get_contents1($dir)
    {
        $realPath = realpath($dir);
        if ($realPath === false)
            return '';
        if ($dir !== $realPath)
        {
            clearstatcache(true);
        }
        return file_get_contents($dir);
    }
    

    【讨论】:

      【解决方案6】:

      我将留下我的第一个答案,因为它仍然是一个有效的答案。 我正在通过实现 clearstatcache(true,$filename) 来改进@DanBray 的答案。

      有两个问题导致了问题。

      第一期

      我已经发布并在问题中进行了编辑。这是一个问题 Nginx 配置。

      这些行:

      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $document_root;

      需要替换为:

      fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root;

      第二期

      第二个问题是我需要在调用之前调用 clearstatcache 文件获取内容。我只想调用 clearstatcache 时 绝对必要,所以我写了一个只清除缓存的函数 当目录包含符号链接时。

      function file_get_contents1234_hard_drives($dir_go_1){
          $realPath = realpath($dir_go_1);
              $myDirectory=opendir(dirname($realPath));        
              while($entryName=readdir($myDirectory)) {
                $dirArray[]=$entryName;
              }
      
              /* Finds extensions of files used for my site theelectronichandbook.tech
              function findexts ($filename) {
                $filename=strtolower($filename);
                $exts=split("[/\\.]", $filename);
                $n=count($exts)-1;
                $exts=$exts[$n];
                return $exts;
              }*/
      
              // Closes directory
              closedir($myDirectory);
      
              // Counts elements in array
              $indexCount=count($dirArray);
              for($ArPos=1;$ArPos<=$indexCount;$ArPos++){
                  /*used for my site theelectronichandbook.tech
                  if($_SERVER['QUERY_STRING']=="hidden"){
                      $H="";
                      $af="./";
                      $atext="Hide";
                  }else{
                      $H=".";
                      $af="./?hidden";
                      $at="Show";
                  }*/
                  if(strpos($dirArray[$ArPos], "Symlink") !== false){
                      clearstatcache(true,$dir_go_1);
                  }
              }
          return file_get_contents($dir_go_1);
      }
      

      我用我的网络服务器测试了上面的代码,它工作正常。

      【讨论】:

      • 不幸的是,它不适用于我的网络服务器。
      • 好吧,我会回到绘图板上。 @丹布雷
      • 非常感谢,但不幸的是,距离赏金期到期的时间已经很短了。但是,如果您想到我 100% 满意的解决方案,我将奖励额外的赏金。还有,file_get_contents1是我做的框架的一部分,所以用的比较多,优化很重要。
      • 那可能需要改成While($dir_go!==null)@DanBray
      【解决方案7】:

      尝试将代码放在使用 Jquery 不断刷新的元素中,并强制重新验证和清除静态捕获。此代码已从@naveed 原始answer 修改。

      form.php:

       <meta http-equiv="Cache-Control" content="no-store, must-revalidate" />
       <meta http-equiv="Expires" content="0"/>
       <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
       <script> 
       jQuery(document).ready(function(){
          jQuery('.ajaxform').submit( function() {
              $.ajax({
                  url     : $(this).attr('action'),
                  type    : $(this).attr('method'),
                  dataType: 'json',
                  data    : $(this).serialize(),
                  success : function( data ) {
                              // loop to set the result(value)
                              // in required div(key)
                              for(var id in data) {
                                  jQuery('#' + id).html( data[id] );
                              }
                            }
              });
              return false;
          });
      });
      var timer, delay = 30;
      timer = setInterval(function(){
          $.ajax({
            type    : 'POST',
            url     : 'profile.php',
            dataType: 'json',
            data    : $('.ajaxform').serialize(),
            success : function(data){
                        for(var id in data) {
                          jQuery('#' + id).html( data[id] );
                        }
                      }
          }); }, delay);
       </script>
       <form action='profile.php' method='post' class='ajaxform'></form>
       <div id='result'></div>
      

      profile.php:

       <?php
             // All form data is in $_POST
             // Now perform actions on form data here and create an result array something like this
             clearstatcache();
             $arr = array( 'result' => file_get_contents("./myfile.text") );
             echo json_encode( $arr );
       ?>
      

      【讨论】:

        猜你喜欢
        • 2023-02-11
        • 1970-01-01
        • 1970-01-01
        • 2017-06-06
        • 1970-01-01
        • 2022-12-17
        • 2021-05-05
        • 2017-09-14
        • 1970-01-01
        相关资源
        最近更新 更多