【问题标题】:Java I/O over an NFS mount基于 NFS 挂载的 Java I/O
【发布时间】:2009-11-24 16:07:53
【问题描述】:

我有一些 Java 代码可以将 XML 文件输出到 NFS 挂载的文件系统。在将文件系统挂载为 Samba 共享的另一台服务器上,有一个进程正在运行,该进程每 30 秒轮询一次新的 XML 文件。如果找到新文件,则会对其进行处理,然后将其重命名为备份文件。 99% 的情况下,文件的写入没有问题。但是,备份文件时不时会包含部分写入的文件。

在与其他人讨论后,我们猜测外部服务器上运行的进程在读取文件时干扰了 Java 输出流。他们建议首先创建一个 .temp 类型的文件,然后在文件写入完成后将其重命名为 .xml。一种常见的行业惯例。更改后,每次重命名都失败。

一些研究表明,在使用 NFS 挂载的文件系统时,Java 文件 I/O 存在错误。

帮助我的 Java 大师!我该如何解决这个问题?

以下是一些相关信息:

  • 我的进程是在 Solaris 10 上运行的 Java 1.6.0_16
  • 挂载的文件系统是 NAS
  • 带有轮询进程的服务器是 Windows Server 2003 R2 Standard, Service Pack 2

这是我的代码示例:

//Write the file
XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
FileOutputStream os = new FileOutputStream(outputDirectory + fileName + ".temp");
serializer.output(doc, os);//doc is a constructed xml document using JDOM
os.flush();
os.close();

//Rename the file
File oldFile = new File(outputDirectory + fileName + ".temp");
File newFile = new File(fileName + ".xml");
boolean success = oldFile.renameTo(newFile);
if (!success) {
    // File was not successfully renamed.
    throw new IOException("The file " + fileName + ".temp could not be renamed.");
}//if

【问题讨论】:

  • 呃,也许是一个愚蠢的问题,但是如果你使用 File newFile = new File(outputDirectory,"fileName"+".xml") ,它会起作用吗?这可能是一个问题 b/c 你不能将文件重命名为不在目录中的东西......

标签: java samba nfs jdom


【解决方案1】:

您可能必须在新文件名中指定完整路径:

File newFile = new File(outputDirectory + fileName + ".xml");

【讨论】:

    【解决方案2】:

    这在我看来是个错误:

    File oldFile = new File(outputDirectory + fileName + ".temp");
    File newFile = new File(fileName + ".xml");
    

    我早就料到了:

    File oldFile = new File(outputDirectory + fileName + ".temp");
    File newFile = new File(outputDirectory + fileName + ".xml");
    

    一般来说,XML 文件的写入和读取/处理/重命名任务之间似乎存在竞争条件。您能否让读取/处理/重命名任务仅对超过 1 分钟的文件或类似文件进行操作?

    或者,让 Java 程序在完成写出 XML 文件后写出一个额外的空文件,表示对 XML 文件的写入已完成。仅当存在信号文件时才读取/处理/重命名 XML 文件。然后删除信号文件。

    【讨论】:

    • 我无法控制读取/处理/重命名任务。这是从第三方软件包运行的任务。
    • 啊,我明白了。在这种情况下,上面的操作系统本机文件锁定示例听起来是最好的选择。
    【解决方案3】:

    原来的错误听起来肯定是并发访问文件的问题——您的解决方案应该有效,但也有替代解决方案。

    例如,在您的自动读取进程上放置一个计时器,以便在检测到新文件时记录文件大小,休眠 X 秒,然后如果大小不匹配,则重新启动计时器。这应该避免部分文件传输的问题。

    编辑:或检查上面之前的时间戳以检查这一点,但请确保它足够老,以至于时间戳中的任何不精确都无关紧要(例如,自上次修改以来的 10 秒到 1 分钟)。

    或者,试试这个:

    File f = new File("foo.xml");
    FileOutputStream fos = new FileOutputStream(f);
    FileChannel fc = fos.getChannel();
    FileLock lock = fc.lock();
    (DO FILE WRITE)
    fis.flush();
    lock.release();
    fos.close();
    

    这应该使用本机操作系统文件锁定来防止其他程序(例如您的 XML 阅读器守护程序)的并发访问。

    就 NFS 故障而言:有一个记录在案的“功能”(错误),其中文件无法通过 Java 中的“重命名”在文件系统之间移动。会不会有混淆,因为它位于 NFS 文件系统上?

    【讨论】:

    • 我无法控制读取/处理/重命名任务。这是从第三方软件包运行的任务。
    • 那么您可能不得不使用上述方法进行文件锁定...我认为应该可以。
    【解决方案4】:

    一般情况下 NFS 的一些信息。根据您的 NFS 设置,锁定可能根本不起作用,并且许多大型 NFS 安装都针对读取性能进行了调整,因此由于缓存效应,新数据出现的时间可能比预期的要晚。

    我看到了您创建文件、添加数据的效果(这是在另一台机器上看到的),但之后的所有数据都出现了 30 秒的延迟。

    顺便说一句,最好的解决方案是旋转文件架构。因此,假设最后一个已写入,而前一个已安全写入并且可以读取。我不会处理单个文件并将其用作“管道”。

    您也可以使用在大文件写入并正确关闭后写入的空文件。因此,如果小家伙在那里,那么大家伙肯定已经完成并且可以阅读。

    【讨论】:

      【解决方案5】:

      可能是由于“重命名操作可能无法将文件从一个文件系统移动到另一个文件系统”来自http://java.sun.com/j2se/1.5.0/docs/api/java/io/File.html#renameTo%28java.io.File%2) 尝试改用 apache commons io FiltUtils.copyFileToDirectory http://commons.apache.org/io/api-release/org/apache/commons/io/FileUtils.html#copyFileToDirectory(java.io.File,%20java.io.File)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-06-09
        • 1970-01-01
        • 1970-01-01
        • 2022-07-28
        • 1970-01-01
        • 1970-01-01
        • 2012-01-20
        相关资源
        最近更新 更多