1

我正在开发一个 Java Web 应用程序,该应用程序使用数千个小文件来构建工件以响应请求。我认为如果我们可以将这些文件映射到内存而不是在整个磁盘上运行以一直查找它们,我们的系统可以看到性能改进。

我听说过 linux 中的 mmap,我对这个概念的基本理解是,当从磁盘读取文件时,文件的内容会缓存在内存中的某个位置,以便更快地进行后续访问。我的想法类似于这个想法,除了我想将整个可 mmap 的文件集读入内存,因为我的 Web 应用程序正在初始化以实现最小的请求时间响应。

我的思路的一个方面是,如果将文件全部打包并以某种方式作为虚拟文件系统安装在 JVM 中,我们可能会更快地将文件放入 jvm 内存。就目前而言,我们当前的实现可能需要几分钟才能遍历源文件集并找出磁盘上的所有内容。这是因为我们实际上是在对超过 300,000 个文件进行文件统计。

我找到了可以从 tar 文件中读取信息的 apache VFS 项目,但是我不确定他们的文档中是否可以指定诸如“另外,将整个 tar 读入内存并将其保存在那里..”之类的内容。

我们在这里谈论的是一个多线程环境,该环境服务于通常将一整套 300,000 多个源文件中的大约 100 个不同文件拼凑在一起以做出一个响应的工件。所以无论虚拟文件系统解决方案是什么,它都需要是线程安全的和高性能的。我们在这里只讨论读取文件,没有写入。

此外,我们正在运行具有 32 GB RAM 的 64 位操作系统,我们的 300,000 个文件占用大约 1.5 到 2.5 GB 的空间。我们肯定可以将 2.5 GB 的文件读入内存比 300K 的几 KB 大小的小文件快得多。

感谢您的输入!

  • 杰森
4

8 回答 8

1

您可以尝试将所有文​​件放在一个 JAR 中并将其放在类路径中。Java 使用一些内置技巧来非常快速地读取 JAR 文件。这也会将所有文件的目录保留在 RAM 中,因此您不必访问磁盘来查找文件(在您开始加载文件之前会发生这种情况)。

JVM 不会一次将整个 JAR 加载到 RAM 中,而且您可能也不希望这样做,因为您的机器会开始交换。但是它将能够非常快速地找到这些片段,因为它将始终保持文件打开,因此,您不会在任何时候打开/关闭文件资源。

此外,由于您一直在使用这个单个文件,因此操作系统可能会将其保留在文件缓存中的时间更长。

最后,您可以尝试压缩 JAR。虽然这听起来可能是个坏主意,但您应该试一试。如果小文件压缩得很好,那么用当前 CPU 解压的时间比从磁盘读取数据的时间要短得多。如果您不必将中间数据保存在任何地方,则可以将未压缩的数据流式传输到客户端,而无需写入文件(这会破坏整个想法)。这样做的缺点是它确实会占用 CPU 周期,如果您的 CPU 很忙(只需使用一些加载工具检查;如果它高于 20%,那么您就会松动),那么您将使整个过程变慢。

也就是说,当您使用 HTTP 协议时,您可以告诉客户端您正在发送压缩数据!这样,您不必解压数据并且可以加载非常小的文件。

JAR 解决方案的主要缺点:只要服务器正在运行,您就无法替换 JAR。因此,替换文件意味着您必须重新启动服务器。

于 2008-12-03T10:29:59.860 回答
1

如果您需要快速访问 300,000 个文件,您可以使用数据库,而不是关系数据库,而是简单的键值数据库,例如http://www.space4j.org/。这对您的启动时间没有帮助,但在运行时可能会大大加快速度。

于 2009-01-18T13:09:55.960 回答
0

您需要的是在HashTable中加载所有信息。

使用文件名作为键加载每个文件,将内容作为值,您将能够比您想象的设置更快、更轻松地工作几个数量级。

于 2008-12-03T14:29:53.183 回答
0

澄清一下,mmap()在类 Unix 系统中不允许您这样访问文件;它只是使文件的内容在内存中可用,作为内存。您不能用于open()进一步打开任何包含的文件。没有所谓的“一mmap()组文件”。

难道你不能添加一个最初加载所有“模板”的通道,然后根据简单的东西快速找到它们,比如每个名称上的哈希?这应该可以让你利用你的记忆,并开始对任何模板进行 O(1) 访问。

于 2008-12-03T10:02:31.023 回答
0

我认为您仍在考虑使用旧的内存/磁盘模式。

mmap在这里无济于事,因为旧的内存/磁盘东西早已不复存在。如果你映射一个文件,内核会给你一个指向一些虚拟内存的指针,供你自行决定使用,它不会立即将文件加载到实际内存中,它会在你请求时这样做文件的一部分,它只会加载您请求的页面。(即一个内存页,通常在 4KB 左右。)

你说那些 300k 的文件,大约需要 1.5GB 到 2.5GB 的磁盘空间。如果有任何机会可以将 2(或更好,4)更多 GB 的 RAM 投入服务器,如果它有足够的 RAM 可以将文件加载到某些磁盘缓存中,那么将磁盘读取内容留给操作系统会更好,它会,并且从他们那里,他们上的任何 read() 甚至都不会击中磁盘。(如果您没有使用 noatime 安装卷,它会将 atime 存储在 inode 中。)

如果您尝试读取()文件,将它们放入内存并从那里提供它们,您现在可以确定它们将始终在RAM中而不是在交换中,因为操作系统还有其他事情要做你有几次没有使用的那部分内存。

如果您有足够的 RAM 让操作系统进行磁盘缓存,并且您确实希望加载文件,那么您总是可以编写一个小脚本/程序来遍历您的层次结构并读取所有文件。(不做任何其他事情。)它将让操作系统将它们从磁盘加载到内存磁盘缓存,但是如果操作系统需要内存,您无法知道它们会留在那里。因此,正如我之前所说,您应该让操作系统处理它并为其提供足够的 RAM 来执行此操作。

您应该阅读varnishArchitect Notes,phk 用他自己的话告诉您,为什么您要实现的目标最好留给操作系统,它永远会更好地了解 JVM 内存中有什么,什么是不是。

于 2008-12-03T10:04:14.810 回答
0

如果您需要快速访问所有这些文件,您可以将它们加载到内存中,但我不会将它们作为文件加载。我会将这些数据放在某种对象结构中(以最简单的形式,只是一个字符串)。

我要做的是创建一个服务,从您使用的任何参数中将文件作为对象结构返回。然后围绕这个服务实现一些缓存机制。然后就是调整缓存的问题了。如果您确实需要将所有内容加载到内存中,请配置缓存以使用更多内存。如果某些文件的使用量比其他文件多得多,则仅缓存那些文件可能就足够了...

如果我们对您想要达到的目标有更多了解,我们可能会给您更好的回应。

于 2008-12-03T10:23:37.703 回答
0

将文件放在 10 个不同的服务器上,而不是直接为请求提供服务,而是向客户端发送 HTTP 重定向(或等效的),并附上他们可以找到所需文件的 URL。这允许分散负载。服务器只响应快速请求,并且(大)下载分布在多台机器上。

于 2008-12-03T10:34:13.313 回答
0

如果您使用的是 Linux,我会尝试使用好的旧RAM 磁盘。您可以坚持当前的做事方式,并大幅降低 IO 成本。您不受 JVM 内存的约束,仍然可以轻松替换内容。

正如您所说的 VFS:它也有一个RAM 磁盘提供程序,但我仍然会首先尝试使用本机 RAM 磁盘方法。

于 2008-12-03T11:00:31.700 回答