为什么 Apache 建议不要在 Linux 上使用带有 NFS 的 sendfile()

pro*_*ark 5 linux apache nfs nginx sendfile

Apache 文档包含以下 EnableSendfile 语句:

使用网络安装的 DocumentRoot(例如,NFS、SMB、CIFS、FUSE),内核可能无法通过自己的缓存为网络文件提供服务。[1]

Apache 2.4 和 Nginx 的默认配置禁用了 sendfile()。

我试图找到一些具体的东西来描述在 Linux 上将 sendfile() 与 NFS 文件系统一起使用时的确切问题。在内核 3.10.0-327.36.3 (CentOS 7) 上运行一个最小的测试程序验证 sendfile() 在源位于 NFS 上时确实有效,并且它确实从页面缓存中读取(第一次运行很慢,随后很快, drop_caches 使其再次变慢,即从源重新读取)。我尝试使用高达 1G 的文件大小,一切似乎都正常。我假设一定有一些情况可以揭示错误行为,但我想确切地知道那是什么。

为了进行比较,有一些关于 VirtualBox 卷在 sendfile()[2] 中存在的问题的文档,但我找不到类似的内容涵盖 Apache,或者如何复制有问题的配置。

Ano*_*non 8

Nginx 的默认配置打开sendfile- https://github.com/nginx/nginx/blob/release-1.13.8/conf/nginx.conf#L27所以我对你在那里的陈述感到困惑。

早在 2000 年代初期,您就可以看到一个Apache 开发人员介绍了禁用 SendFile 的选项(这里是补丁邮件列表帖子)。还有一些旧的错误可能与 Apache 错误跟踪器中的 sendfile 相关。从Apache 错误 #12893我们了解到,其中一个失败是因为 Linux 内核中的 NTFS 实现根本不支持sendfile系统调用:

[...] 显然,您的 NTFS 文件系统的某些特征会阻止 sendfile() 工作。

sendfile(8, 9, [0], 9804)               = -1 EINVAL (Invalid argument)
Run Code Online (Sandbox Code Playgroud)

一个博客文章题为“SENDFILE和Apache的神秘案件”引用您正在阅读提出了以下理论计算器问题:

sendfile() 最多传输 0x7ffff000 (2,147,479,552) 个字节,返回实际传输的字节数。(在 32 位和 64 位系统上都是如此。)

有 2GB 的限制。现在这是假设,apache 文档说:

使用网络安装的 DocumentRoot(例如,NFS、SMB、CIFS、FUSE),内核可能无法通过自己的缓存提供网络文件 [2]

因此,当它说“内核可能无法提供文件”时,我认为我们在这里可能指的是 sendfile 对文件大小的固有限制。

有趣的理论,但我怀疑这是答案,因为您可以简单地选择不对太大的文件使用 sendfile 代码路径。更新:在四处挖掘时,我发现那篇文章的作者创建了一个后续文章,标题为我错了关于 Sendfile() 和 Apache,其中提到了您正在阅读的答案!

ProFTPD 文档中也有关于 sendfile 问题的警告

在某些情况下,文件系统而不是内核似乎是 sendfile(2) 问题的罪魁祸首:

  • 网络文件系统(例如 NFS、SMBFS/Samba、CIFS)
  • 虚拟化文件系统(OpenVZ、VMware,甚至 Veritas)
  • 其他文件系统(例如 Linux 上的 NTFS 和 tmpfs)

同样,如果您在从 ProFTPD 下载文件时遇到问题,而这些文件驻留在网络或虚拟化文件系统上,请尝试在您的 proftpd.conf 中使用“UseSendfile off”。

很多“这里有龙”警告。其中一些是因为文件系统根本不支持 sendfile (例如,直到 2.4.22-pre3 Linux 的 tmpfs 不支持 sendfile)。基于 FUSE 的文件系统(例如 NTFS-3g)在过去也会由于 FUSE 和 sendfile 错误(因为已解决)而出现问题。不过,虚拟化文件系统的列表是一个有趣的补充……

然而,OrangeFS FAQ似乎有最合理的解释:

5.16 我们可以运行 Apache 网络服务器来从 orangefs 卷中提供文件吗?

你当然可以!但是,我们建议您在启动 Web 服务器之前关闭 httpd.conf 中的 EnableSendfile 选项。或者,您可以使用选项 -enable-kernel-sendfile 配置 orangefs。传递此选项以在支持 sendfile 回调的 orangefs 内核模块中配置结果。但我们建议,除非提供的文件足够大,否则就性能而言这可能不是一个好主意。Apache 2.x+ 使用sendfile 系统调用,通常通过 page-cache 暂存文件数据。在最近的2.6 内核上,这可以通过在文件系统中提供 sendfile 回调例程来避免. 因此,这可确保我们最终不会在此类内核上获得陈旧或不一致的缓存数据。但是,在较旧的 2.4 内核上,sendfile 系统调用通过页面缓存流式传输数据,因此数据很有可能过时。因此,sendfile 系统调用的用户被警告要小心这个细节。

类似的解释可以在Linux guest readv system call returns stale (cached) shared folder file data Virtualbox bug 中阅读

我发现使用 read 系统调用读取文件的程序返回正确的数据,但那些使用 readv 系统调用的程序(例如我的 gas 版本)读取过时的缓存数据。

[...]

使用核函数 generic_file_read_iter 作为 file_operations 结构的 .read_iter 成员(.read_iter 在执行 readv 系统调用时使用)。此函数将写入和读取文件缓存。但是,用于通用 .read 成员和 read 系统调用的 vbox 函数 sf_reg_read 似乎总是绕过 Linux 的 FS 缓存。

[...]

此外,我认为类似的长期问题报告为票证#819,仅适用于 sendfile 系统调用。似乎所有这些 generic_file_* 函数都期望主机控制对驱动器的所有访问。

以上也可以解释 ProFTPD 的问题虚拟化文件系统列表。

总结(最佳猜测)

Apache 建议不要sendfile()与 Linux NFS 一起使用,因为他们的软件很流行,并且在sendfile使用旧的 Linux NFS 客户端调试相关错误时会引发许多痛苦的事情。警告是旧的,保持原样可能更容易,而不是用所有警告更新它。

如果您有一个 Linux 文件系统,其中可以在不使 Linux 页面缓存失效的情况下更改底层数据sendfile,那么在旧的 Linux 内核上使用它是不明智的(这解释了旧的 Linux NFS 客户端问题)。对于较新的内核,如果上述文件系统没有实现自己的sendfile钩子,再次使用sendfile是不明智的(Virtualbox 共享文件夹问题证明了这一点)。

最近的(2.6.31 及更高版本)Linux 内核为可能面临此失效问题的文件系统提供了使用自己的sendfile实现的工具,并假设文件系统可以使用它来sendfile阻止错误,但请注意空客!