Ввод/вывод Java через монтирование NFS

У меня есть немного кода Java, который выводит файл XML в файловую систему, смонтированную NFS. На другом сервере, на котором файловая система смонтирована как общий ресурс Samba, запущен процесс, который опрашивает новые XML-файлы каждые 30 секунд. Если найден новый файл, он обрабатывается, а затем переименовывается в файл резервной копии. В 99% случаев файлы записываются без проблем. Однако время от времени файл резервной копии содержит частично записанный файл.

После некоторого обсуждения с некоторыми другими людьми мы предположили, что процесс, работающий на внешнем сервере, мешает выходному потоку Java при чтении файла. Они предложили сначала создать файл типа .temp, который затем будет переименован в .xml после завершения записи файла. Обычная отраслевая практика. После изменения переименовать каждый раз не получается.

Некоторые исследования показали, что файловый ввод-вывод Java вызывает ошибки при работе с файловыми системами, смонтированными NFS.

Помогите мне, гуру Java! Как решить эту проблему?

Вот некоторая актуальная информация:

  • Мой процесс - Java 1.6.0_16, работающий на Solaris 10.
  • Смонтированная файловая система — это 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

person Melvin    schedule 24.11.2009    source источник
comment
Эээ, может быть, глупый вопрос, но работает ли это, если вы используете File newFile = new File(outputDirectory,"fileName"+".xml")? Это может быть проблемой, потому что вы не можете переименовать файл во что-то не в каталоге...   -  person BobMcGee    schedule 24.11.2009


Ответы (5)


Возможно, вам придется указать полный путь в новом имени файла:

File newFile = new File(outputDirectory + fileName + ".xml");
person jarnbjo    schedule 24.11.2009

Это выглядит как ошибка для меня:

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-файла только при наличии сигнального файла. Затем удалите файл сигнала.

person shadit    schedule 24.11.2009
comment
У меня нет контроля над задачей чтения/обработки/переименования. Это задача, выполняемая из стороннего программного пакета. - person Melvin; 24.11.2009
comment
А, понятно. В этом случае приведенный выше пример собственной блокировки файлов ОС звучит как лучший вариант. - person shadit; 24.11.2009

Исходная ошибка определенно звучит как проблема с одновременным доступом к файлу — ваше решение должно было сработать, но есть и альтернативные решения.

Например, установите таймер для процесса автоматического чтения, чтобы при обнаружении нового файла он записывал размер файла, засыпал на 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?

person BobMcGee    schedule 24.11.2009
comment
У меня нет контроля над задачей чтения/обработки/переименования. Это задача, выполняемая из стороннего программного пакета. - person Melvin; 24.11.2009
comment
Тогда вам, возможно, придется прибегнуть к блокировке файлов, используя описанный выше метод... который, я думаю, должен работать. - person BobMcGee; 24.11.2009

Немного информации о NFS в целом. В зависимости от ваших настроек NFS блокировки могут вообще не работать, и многие большие установки NFS настроены на производительность чтения, поэтому новые данные могут появиться позже, чем ожидалось, из-за эффектов кэширования.

Я видел эффекты, когда вы создавали файл, добавляли данные (это было видно на другой машине), но все данные после этого появлялись с 30-секундной задержкой.

Между прочим, лучшее решение — это вращающаяся файловая схема. Так что последний считается записанным, а предыдущий благополучно записан и может быть прочитан. Я бы не стал работать над одним файлом и использовать его как "трубу".

В качестве альтернативы вы можете использовать пустой файл, который записывается после того, как большой файл был записан и правильно закрыт. Так что, если маленькие ребята там есть, то большой парень был окончательно готов и его можно прочитать.

person ReneS    schedule 24.11.2009

Возможно, из-за того, что «операция переименования не может переместить файл из одной файловой системы в другую» из 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) вместо этого

person Kennet    schedule 24.11.2009