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


23

Некоторые программы копирования файлов любят rsyncи curlмогут возобновлять неудачные операции передачи / копирования.

Отмечая, что может быть много причин этих сбоев, в некоторых случаях программа может выполнить «очистку», а в некоторых случаях - нет.

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

например, размер фрагмента файла, «сделавшего его» по назначению, составляет 1378 байт, поэтому они просто начинают чтение с байта 1379 оригинала и добавляют его к фрагменту.

Мой вопрос заключается в том, что, зная, что байты состоят из битов, а не во всех файлах данные сегментированы на куски чистого байтового размера, откуда эти программы знают, что точка, к которой они решили добавить данные, верна?

При записи файла назначения происходит ли какая-либо буферизация или «транзакции», аналогичные базам данных SQL, на уровне программы, ядра или файловой системы, чтобы гарантировать, что только чистые, правильно сформированные байты попадают в базовое блочное устройство?
Или программы предполагают, что последний байт будет потенциально неполным, поэтому они удаляют его в предположении, что он плохой, перезаписывают байт и начинают добавление оттуда?

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

Когда эти программы «возобновляются», как они узнают, что запускаются в нужном месте?


21
"не все файлы имеют свои данные, сегментированные на чистые куски размером в байты", не так ли? Как вы пишете что-нибудь меньше, чем байт в файл?
Муру

17
Я не знаю ни одного системного вызова, которое могло бы записать что-либо меньше байта, а что касается самого диска, я думаю, что сегодня ни один диск не записывает менее 512 байтовых блоков (или 4096 байтовых блоков).
Муру,

8
Нет, я говорю, что минимум - это байт. Разумные приложения будут использовать блоки по 4 КБ или 8 КБ: head -c 20480 /dev/zero | strace -e write tee foo >/dev/nullи ОС будет их буферизовать и отправлять на диск еще большими кусками.
Муру

9
@the_velour_fog: Как ты пишешь только один бит с fwrite()?
psmears

9
Для всех практических целей, данных будет составлен из байтов , и все работает с ними как наименьшая единица. Некоторые системы (в основном относящиеся к сжатию, например, gzip, h264) распаковывают отдельные биты из байтов, но операционная система и память работают на уровне байтов.
pjc50

Ответы:


40

Для ясности - реальная механика более сложна для обеспечения еще большей безопасности - вы можете представить себе операцию записи на диск следующим образом:

  • приложение записывает байты (1)
  • ядро (и / или файловая система IOSS) буферизует их
  • как только буфер заполнен, он сбрасывается в файловую систему:
    • блок выделен (2)
    • блок написан (3)
    • информация о файле и блоке обновлена ​​(4)

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

Если в (2), ничего не происходит, кроме как в памяти. То же, что (1). Если в (3) данные записываются, но никто не помнит об этом . Вы отправили 9000 байт, 4096 записано, 4096 записано и потеряно , остальное просто потеряно. Передача возобновляется по смещению 4096.

Если в (4) данные должны были быть зафиксированы на диске. Следующие байты в потоке могут быть потеряны. Вы отправили 9000 байт, 8192 записаны, остальное потеряно, передача возобновляется со смещением 8192.

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

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

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


1
Отличное объяснение. это все имеет много смысла. таким образом, если процесс действительно прошел весь путь до (4) информации о блокировке файла, вы знаете, что все эти байты хороши. затем любые байты, которые были на любом предыдущем этапе, либо не попали на диск, либо - если они это сделали - они были бы "не запомнены" (без ссылок на них)
the_velour_fog

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

Этот ответ преувеличивает полезность ведения журналов в файловых системах. Он не будет надежно работать, если все не реализует семантику транзакций, включая приложения пользовательского пространства (через fsync) и контроллер жесткого диска (часто не работающий даже на предположительно «корпоративных» дисках). Без fsyncмногих файловых операций, которые интуитивно упорядочены и атомарны, POSIX не гарантирует, что файлы, открытые с помощью, O_APPENDмогут вести себя иначе, чем без и т. Д. На практике наиболее важными ключами для согласованности файлов являются система ядра VFS и дисковый кэш. Все остальное в основном пух.
user1643723

11

Примечание: я не смотрел на источники rsyncили любую другую утилиту передачи файлов.

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

Обе операции выполняются одним вызовом стандартной библиотечной функции языка Си lseek()( lseek(fd, 0, SEEK_END)возвращает длину файла, открытого для файлового дескриптора fd, в байтах).

Как только это будет сделано для целевого файла, подобный вызов lseek()может быть сделан на исходный файл , чтобы перейти в соответствующее положение: lseek(fd, pos, SEEK_SET). Передача может затем продолжаться в этой точке, при условии, что более ранняя часть исходного файла была идентифицирована как неизмененная (разные утилиты могут делать это по-разному).

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


Что касается обсуждения в комментариях о битах и ​​байтах: наименьшая единица данных, которая может быть записана на диск, - это байт . Один байт требует, чтобы на диске был размещен хотя бы один блок данных. Размер блока зависит от типа файловой системы и, возможно, также от параметров, используемых администратором при инициализации файловой системы, но обычно он находится между 512 байтами и 4 КиБ. Операции записи могут быть буферизованы ядром, базовой библиотекой C или самим приложением, и фактическая запись на диск может происходить в виде кратных значений соответствующего размера блока в качестве оптимизации.

Невозможно записать отдельные биты в файл, и если операция записи завершится неудачей, она не оставит в файле «наполовину записанные байты».


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

6
@the_velour_fog такого рода неожиданное завершение не может произойти, потому что процесс будет бесперебойным в середине системного вызова ввода-вывода (вот почему нет ничего необычного в том, что неубиваемый процесс застревает в вызовах доступа к файловой системе для файла NFS). См. Также: unix.stackexchange.com/q/62697/70524
Muru

2
Могут возникнуть проблемы, если система теряет питание в самый неподходящий момент. Это может иногда приводить к мусору в последней точке записи файла. Это очень сложная проблема в проектировании баз данных. Но все же обычная наименьшая единица, которая является «действительной» или «недействительной», является дисковым блоком.
pjc50

1
@the_velour_fog Это не так много, как вы не можете получить « наполовину записанные байты » (или, точнее, наполовину записанный блок байтов), поскольку наполовину записанный блок не будет записан как записанный (полностью) ) - см. шаги (3) и (4) ответа Лерни .
TripeHound

5

Это в основном два вопроса, потому что такие программы, как curl и rsync, очень разные.

Для клиентов HTTP, таких как curl, они проверяют размер текущего файла и затем отправляют Content-Rangeзаголовок со своим запросом. Сервер либо возобновляет отправку диапазона файла, используя код состояния 206(частичное содержимое) вместо 200(успех), и загрузка возобновляется, либо игнорирует заголовок и начинается с начала, и у HTTP-клиента нет другого выбора, кроме повторной загрузки всего опять таки.

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

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

С другой стороны, rsync - это расширенный протокол для инкрементной передачи файлов. Он генерирует контрольные суммы частей файла на стороне сервера и клиента, чтобы определить, какие байты совпадают. Тогда это только отправляет различия. Это означает, что он не может только возобновить загрузку, но даже может загрузить измененные байты, если вы изменили несколько байтов в середине очень большого файла без повторной загрузки файла.

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

Обратите внимание, что rsync и bittorent проверит частичные данные на вашем диске, а возобновление загрузки по HTTP - нет. Поэтому, если вы подозреваете, что частичные данные повреждены, вам необходимо проверить целостность в противном случае, то есть, используя контрольную сумму окончательного файла. Но простое прерывание загрузки или потеря сетевого подключения обычно не приводит к повреждению частичного файла, в то время как может произойти сбой питания во время передачи.


4

TL; DR: они не могут, если протокол, который они используют, не допускает этого.

Программы не всегда могут возобновить работу с произвольного места: например, HTTP-запросы перезапускаются только в том случае, если сервер поддерживает их, а клиент реализует их: это не универсально, поэтому проверьте документацию вашей программы. Если сервер его поддерживает, программы могут возобновить передачу, просто запросив часть протокола. Обычно вы видите частичные переносы в вашем каталоге загрузки (они обычно помечаются расширением «.partial» или чем-то подобным.)

Если загрузка файла приостановлена ​​или иным образом остановлена, клиент может записать файл на диск и иметь четкое представление о том, где продолжить. Если, с другой стороны, происходит сбой клиента или возникает ошибка записи в файл, клиент должен предположить, что файл поврежден, и начать все сначала. BitTorrent несколько смягчает это, разбивая файлы на «куски» и отслеживая, какие из них были успешно загружены; самое большее, что ему когда-либо придется переделывать, это несколько кусков. Rsync делает что-то подобное.

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

Если вы хотите более точную проверку, HTTP и друзья не должны быть вашим первым выбором. Вы захотите использовать протокол, который также имеет контрольную сумму или хэш для всего файла и каждого переданного блока, чтобы вы могли сравнить контрольную сумму загрузки с контрольной суммой компьютера сервера: все, что не соответствует, будет затем повторно загружено. Опять же, BitTorrent является примером такого протокола; При желании rsync может сделать это тоже.


для примера rsync это будет просто, потому что существует только один протокол rsync. для http-загрузок в качестве стандарта используется запрос диапазона. Мне любопытно узнать, что на самом деле делает curl при возобновлении загрузки, потому что стандартная семантика загрузки - это multipart / form-data (для wget и curl), но я не верю, что семантика резюме загрузки общепризнана. Например, YouTube и Nginx могут делать это по-разному.
Роб

1

Зависит от протокола, используемого для передачи. Но curl использует http и передает данные последовательно в порядке их появления в файле. Таким образом, curl может возобновиться в зависимости от размера файла частично завершенной передачи. Фактически, вы можете обмануть его, чтобы пропустить первые N байтов, создав файл длиной N (чего угодно) и попросив его обработать этот файл как частично завершенную загрузку (а затем отбросив первые N байтов).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.