PHP file_exists有时会为 CIFS 共享上的文件返回 false


PHP file_exists sometimes returns false for a file on CIFS share

>我有一个来自Windows Server 2012 R2的CIFS共享挂载在Ubuntu 14.04.2 LTS(内核3.13.0-61泛型)上,像这样

/

etc/fstab

//10.1.2.3/Share /Share cifs credentials=/root/.smbcredentials/share_user,user=share_user,dirmode=0770,filemode=0660,uid=4000,gid=5000,forceuid,forcegid,noserverino,cache=none 0 0

gid=5000对应于运行 PHP 进程的组www-data

当我通过以www-data用户身份登录的控制台进行检查时,这些文件已正确挂载 - 它们是可读和可删除的(PHP 脚本使用的操作)。

PHP 脚本每天处理大约 50-70,000 个文件。这些文件是在主机Windows机器上创建的,一段时间后,在Linux机器上运行的PHP脚本会收到有关新文件的通知,检查该文件是否存在(file_exists),读取并删除该文件。通常一切正常,但有时(每天几百到1-2000个)PHP脚本会引发文件不存在的错误。这种情况永远不应该是这样,因为它只通知实际存在的文件。

当我手动检查那些报告为不存在的文件时,它们可以在 Ubuntu 机器上正确访问,并且创建日期在 PHP 脚本检查它们存在之前。

然后我手动触发 PHP 脚本来拾取该文件,并且毫无问题地拾取它。

我已经尝试过的

有多个类似的问题,但我似乎已经用尽了所有的建议:

  • 我在检查之前添加了clearstatcache() file_exists($f)
  • 文件和目录权限正常(稍后会正确选取完全相同的文件)
  • 用于检查file_exists($f)的路径是没有特殊字符的绝对路径 - 文件路径始终具有/Share/11/222/333.zip格式(具有各种数字)
  • 我使用了noserverino共享挂载参数
  • 我使用了cache=none共享挂载参数

/proc/fs/cifs/Stats/显示如下,但我不知道这里是否有任何可疑之处。有问题的份额是2) ''10.1.2.3'Share

Resources in use
CIFS Session: 1
Share (unique mount targets): 2
SMB Request/Response Buffer: 1 Pool size: 5
SMB Small Req/Resp Buffer: 1 Pool size: 30
Operations (MIDs): 0
6 session 2 share reconnects
Total vfs operations: 133925492 maximum at one time: 11
1) ''10.1.2.3'Share_Archive
SMBs: 53824700 Oplocks breaks: 12
Reads:  699 Bytes: 42507881
Writes: 49175075 Bytes: 801182924574
Flushes: 0
Locks: 12 HardLinks: 0 Symlinks: 0
Opens: 539845 Closes: 539844 Deletes: 156848
Posix Opens: 0 Posix Mkdirs: 0
Mkdirs: 133 Rmdirs: 0
Renames: 0 T2 Renames 0
FindFirst: 21 FNext 28 FClose 0
2) ''10.1.2.3'Share
SMBs: 50466376 Oplocks breaks: 1082284
Reads:  39430299 Bytes: 2255596161939
Writes: 2602 Bytes: 42507782
Flushes: 0
Locks: 1082284 HardLinks: 0 Symlinks: 0
Opens: 2705841 Closes: 2705841 Deletes: 539832
Posix Opens: 0 Posix Mkdirs: 0
Mkdirs: 0 Rmdirs: 0
Renames: 0 T2 Renames 0
FindFirst: 227401 FNext 1422 FClose 0

我想我看到的一种模式是,仅当 PHP 脚本之前已经处理(读取和删除)了相关文件时,才会引发错误。有许多文件已正确处理,然后稍后再次处理,但我从未见过第一次处理的文件出现该错误。重新处理之间的时间从 1 天到大约 20 天不等。对于重新处理,只需在 Windows 主机上的相同路径下重新创建文件,其中包含更新的内容。

可能是什么问题?如何更好地调查?如何确定问题出在 PHP 端还是操作系统端?


更新

我已将生成文件的软件移动到以相同方式挂载相同共享的 Ubuntu VM。此组件是用 Java 编写的。我在读取/写入文件时没有看到任何问题。


更新 - PHP 详细信息

确切的 PHP 代码是:

$strFile = zipPath($intApplicationNumber);
clearstatcache();
if(!file_exists($strFile)){
    return responseInternalError('ZIP file does not exist', $strFile);
}

intApplicationNumber是一个请求参数(例如。 12345678 ),它被简单地转换为路径zipPath()函数(例如。 'Share'12'345'678.zip - 始终是完整的路径)。

该脚本可以与不同的应用程序编号同时调用,但不会与相同的应用程序编号同时调用。

如果脚本失败(返回'ZIP file does not exist'错误),将在一分钟后再次调用它。如果失败,它将永久标记为失败。然后,通常一个多小时后,我可以使用在生产环境中完成的相同调用(GET 请求)手动调用脚本并且工作正常,在响应中找到并发送文件:

public static function ResponseRaw($strFile){
    ob_end_clean();
    self::ReadFileChunked($strFile, false);
    exit;
}
protected static function ReadFileChunked($strFile, $blnReturnBytes=true) {
    $intChunkSize = 1048576; // 1M
    $strBuffer = '';
    $intCount = 0;
    $fh = fopen($strFile, 'rb');
    if($fh === false){
        return false;
    }
    while(!feof($fh)){
        $strBuffer = fread($fh, $intChunkSize);
        echo $strBuffer;
        if($blnReturnBytes){
            $intCount += strlen($strBuffer);
        }
    }
    $blnStatus = fclose($fh);
    if($blnReturnBytes && $blnStatus){
        return $intCount;
    }
    return $blnStatus;
}

客户端收到文件后,他通知 PHP 服务器可以将文件移动到存档位置(通过 copy()unlink() )。这部分工作正常。


跟踪结果

几天没有错误后,错误再次出现。我运行了strace,它报告了

access("/Share/11/222/333.zip", F_OK) = -1 ENOENT (No such file or directory)

对于从命令行运行ls /Share/11/222/333.zip时确实存在的一些文件。因此,问题出在操作系统级别,不应责怪PHP。

当主机上磁盘上的负载增加(由于其他进程)时,错误开始出现,因此@risyasin下面的建议似乎最有可能 - 这是繁忙的资源/超时问题。

我将尝试@miguel-svq的建议,跳过存在性测试,然后立即进行fopen()并处理错误。我会看看它是否会改变任何东西。

您可以尝试使用 directio 选项来避免对在此装载上打开的文件执行 inode 数据缓存:

//10.1.2.3/Share /Share cifs credentials=/root/.smbcredentials/share_user,user=share_user,dirmode=0770,filemode=0660,uid=4000,gid=5000,forceuid,forcegid,noserverino,cache=none,directio 0 0

这几乎不是我问题的明确答案,而是对我发现的内容和我解决的问题的总结。

问题的根源在于是操作系统报告该文件不存在。偶尔跑步strace节目

access("/Share/11/222/333.zip", F_OK) = -1 ENOENT (No such file or directory)

对于确实存在的文件(并在与 ls 一起列出时显示)。

Windows 共享主机有时处于沉重的磁盘负载下。我所做的是将其中一个共享移动到不同的主机,以便负载现在在两者之间分配。此外,最近系统上的一般负载有点轻。每当我收到有关文件不存在的错误时,我都会在一段时间后重试请求,它不再存在。