2

我有一些 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
4

5 回答 5

2

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

File newFile = new File(outputDirectory + fileName + ".xml");
于 2009-11-24T16:29:42.987 回答
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 文件。然后删除信号文件。

于 2009-11-24T16:30:51.097 回答
2

最初的错误听起来绝对像是并发访问文件的问题——您的解决方案应该有效,但也有替代解决方案。

例如,在您的自动读取进程上放置一个计时器,以便在检测到新文件时记录文件大小,休眠 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 文件系统上,会不会造成混淆?

于 2009-11-24T16:38:19.770 回答
1

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

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

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

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

于 2009-11-24T17:20:52.913 回答
0

可能是由于“重命名操作可能无法将文件从一个文件系统移动到另一个文件系统”来自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)代替

于 2009-11-24T16:49:37.173 回答