Как обеспечить целостность файла при сбое записи файла? - PullRequest
3 голосов
/ 20 января 2009

Follow to : Как безопасно обновить файл, который имеет много читателей и один писатель?

В моих предыдущих вопросах я выяснил, что вы можете использовать блокировку FileChannel для обеспечения порядка чтения и записи.

Но как вы справляетесь со случаем, если писателю не удается в середине записи (скажем, сбой JVM)? Этот базовый алгоритм будет выглядеть так:

WRITER:
  lock file
  write file
  release file

READER:
  lock file
  read file
  release file

Если JVM дает сбой во время write file, обязательно снимите блокировку, но теперь у меня есть неполный файл. Я хочу, чтобы что-то законченное всегда было читабельным. Либо старый контент, новый контент и ничего промежуточного.

Моя первая стратегия состояла в том, чтобы записать во временный файл, а затем скопировать содержимое в «живой» файл (при этом обеспечив хорошую блокировку). Алгоритм для этого,

WRITER:
  lock temp file
  write temp file
  lock file
  copy temp to file
  release file
  release temp
  delete temp

READER:
  lock file
  read file
  release file

Одна приятная вещь - delete temp не удалит темп, если он уже заблокирован другим автором.

Но этот алгоритм не обрабатывает, если JVM дает сбой во время copy temp to file. Затем я добавил флаг copying,

WRITER:
  lock temp file
  write temp file
  lock file
  create copying flag
  copy temp to file
  delete copying flag
  release file
  release temp
  delete temp

READER:
  lock file
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release file

Никогда не будет двух вещей, получающих доступ к файлу copying, поскольку он защищен блокировкой файла.

Теперь, это способ сделать это? Кажется очень сложным обеспечить что-то очень простое. Есть ли какая-нибудь библиотека Java, которая обрабатывает это для меня?

EDIT

Ну, мне удалось ошибиться в третьей попытке. Читатель не удерживает блокировку температуры, когда он делает copy temp to file. Также это не простое исправление, чтобы просто заблокировать временный файл! Это может привести к тому, что писатель и читатель получат блокировки в разных порядках и могут привести к тупику. Это становится все сложнее все время. Вот моя четвертая попытка,

WRITER:
  lock file
  write temp file
  create copying flag
  copy temp to file
  delete copying flag
  delete temp
  release file

READER:
  lock file
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release file

На этот раз временный файл защищен основной блокировкой, поэтому ему даже не нужна собственная блокировка.

РЕДАКТИРОВАТЬ 2

Когда я говорю о сбое JVM, я на самом деле имею в виду, что питание отключилось, а у вас не было ИБП.

РЕДАКТИРОВАТЬ 3

Мне все-таки удалось сделать еще одну ошибку. Вы не должны блокировать файл, в который вы пишете или читаете. Это вызовет проблемы, поскольку вы не можете получить блокировку чтения и записи, если не используете RandomAccessFile в Java, которая не реализует поток ввода / вывода.

Вместо этого вы просто хотите заблокировать файл блокировки, который защищает файл, который вы читаете или записываете. Вот обновленный алгоритм:

WRITER:
  lock
  write temp file
  create copying flag
  copy temp to file
  delete copying flag
  delete temp
  release

READER:
  lock
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release

блокирует и освобождает файл, временный файл и флаг копирования. Единственная проблема в том, что теперь блокировка считывателя не может быть разделена, но на самом деле это никогда не может быть. У читателя всегда была возможность изменить файл, поэтому было бы неправильно сначала создавать разделяемую блокировку.

Ответы [ 5 ]

1 голос
/ 04 апреля 2011

, несмотря на то, что не существует пуленепробиваемого решения для нескольких операционных систем и для нескольких систем, стратегия «запись в уникальный временный файл и переименование» по-прежнему остается лучшим вариантом. большинство платформ / файловых систем пытаются сделать переименование файлов (эффективно) атомарным. обратите внимание, что вы хотите использовать + отдельный + файл блокировки для блокировки.

Итак, если вы хотите обновить "myfile.txt":

  • блокировка "myfile.txt.lck" (это отдельный пустой файл)
  • записать изменения в уникальный временный файл, например, "myfile.txt.13424.tmp" (используйте File.createTempFile())
  • для дополнительной защиты, но, возможно, медленнее, fsync временный файл перед продолжением (FileChannel.force(true)).
  • переименуйте «myfile.txt.13424.tmp» в «myfile.txt»
  • разблокировать "myfile.txt.lck"

на некоторых платформах (windows) вам нужно немного больше танцевать из-за ограничений на операции с файлами (вы можете переместить "myfile.txt" в "myfile.txt.old" перед переименованием и использовать ".old" файл для восстановления, если вам нужно при чтении).

0 голосов
/ 18 мая 2011

Решение: используйте 2 файла и пишите в них последовательно.
Только 1 запись может потерпеть неудачу.

0 голосов
/ 21 января 2009

Я полагаю, у вас есть большой файл, который вы постоянно добавляете. Сбои ВМ происходят не очень часто. Но если они происходят, вам нужен способ откатить неудачные изменения. Вам просто нужен способ узнать, как далеко откатиться. Например, записав последнюю длину файла в новый файл:

WRITER:
  lock file
  write file position to pos-file
  write file
  remove pos-file
  unlock file

В случае сбоя писателя один из ваших читателей получит блокировку чтения. Они должны проверить pos-файл. Если они найдут один, произошел сбой. Если они заглянут внутрь файла, они знают, как далеко откатить изменения, чтобы получить согласованный файл снова. Конечно, процедура отката должна происходить аналогично процедуре записи.

Когда вы не добавляете, а заменяете файл, вы можете использовать тот же метод:

WRITER:
  lock file
  write writing-in-progress-file
  write file
  remove writing-in-progress-file
  unlock file

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

0 голосов
/ 04 апреля 2011

Без некоторой поддержки операционной системы невозможно, чтобы произвольно сложные файловые операции были атомарными, не требуя программ, которые открывают файл для завершения или отката операций, которые могли быть в процессе. Если это предупреждение приемлемо, можно сделать что-то вроде следующего:

  1. В начале файла, не перекрывая 512-байтовую границу, добавьте два 4- или 8-байтовых числа (в зависимости от максимального размера файла), указывающих логическую длину файла и местоположение записи обновления ( если есть) Большую часть времени значение записи обновления будет равно нулю; акт записи ненулевого значения передаст последовательность обновления; оно будет перезаписано с нуля, когда последовательность обновления будет завершена.
  2. Перед началом последовательности обновления определите верхнюю границу для логической длины файла, когда последовательность обновления завершена (есть способы обойти это ограничение, но они добавляют сложность)
  3. Чтобы запустить последовательность обновления, найдите в файле расстояние, которое больше его текущей логической длины или (верхней границы) будущей логической длины, а затем запишите записи обновления, каждая из которых состоит из смещения файла, количество байтов для записи и данные для записи. Запишите столько записей, сколько требуется для выполнения всех обновлений, и закончите записью, имеющей 0 смещений и 0 длин.
  4. Чтобы зафиксировать последовательность обновлений, сбросьте все ожидающие записи и дождитесь их завершения, а затем запишите новую длину файла и местоположение первой записи обновления.
  5. Наконец, обновите файл, последовательно обработав все записи обновления, сбросив все записи и ожидая их завершения, установив местоположение записи обновления в ноль и (необязательно) обрезав файл до его логической длины.
  6. Если сделана попытка открыть файл, в котором местоположение последовательности обновления не равно нулю, завершите выполнение любых ожидающих записей (используя последний шаг, описанный выше), прежде чем делать что-либо еще с ним.

Если исходная операция записи файла завершится неудачно до того, как будет записана папка записи обновления, все операции записи будут эффективно проигнорированы. Если произойдет сбой после записи местоположения записи обновления, но перед его очисткой, при следующем открытии файла все операции записи будут зафиксированы (некоторые из них, возможно, уже были выполнены, но повторное их выполнение должно быть безвредным). , Если произойдет сбой после записи местоположения записи обновления, обновление файла будет завершено и сбой не повлияет на него вообще.

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

0 голосов
/ 20 января 2009

Я не думаю, что есть идеальный ответ. Я точно не знаю, что вам нужно делать, но можете ли вы записать новый файл, а затем, в случае успеха, переименовать файлы, а не копировать. Переименование происходит быстро и, следовательно, должно быть менее подвержено сбоям. Это все равно не поможет, если произойдет сбой на этапе переименования, но вы свернули окно риска.

Опять же, я не уверен, применимо ли это к вашим потребностям или нет, но вы можете написать какой-нибудь блок файла в конце файла, чтобы показать, что все данные были записаны?

...