Linux

Атомарные изменения при доступе к каталогу, используя rsync

Я делаю несколько ночных и еженедельных «зеркал» часто используемых репозиториев для локальной сети. В нескольких случаях кто-то пытался выполнить обновление во время выполнения rsync и терпел неудачу, потому что ожидаемые файлы еще не все там.

Можно ли выполнить rsync так, чтобы все измененные файлы появлялись только с правильными именами после завершения? Я знаю, что rsync использует временные .hidden файлы во время каждой передачи, но могу ли я как-то отложить переименование до ее завершения? В качестве альтернативы кажется, что я могу использовать опцию --backup для перемещения всех изменений в один каталог и атомарного перемещения их после, но я бы хотел, чтобы эта функция работала в обратном порядке, чем сейчас.

Ответ 1

Вы можете использовать опцию --link-dest=. По сути, вы создадите новую папку, все файлы будут жестко привязаны к новой папке. Когда все будет готово, вы можете просто поменять имена папок местами и удалить старую. В Linux невозможно сделать это на 100% атомарно, так как нет поддержки ядра/VFS. Однако, обмен именами – это всего лишь 2 системных вызова, так что на это должно уйти меньше 1 секунды. Это возможно только на Darwin (MAC/OSX) с помощью системного вызова exchangedata на файловой системе HFS.

Ответ 2

Я делаю нечто подобное с резервным копированием rsync (на диск) и столкнулся с той же проблемой из-за того, что демон обновляет файлы во время выполнения резервного копирования. В отличие от многих программ, rsync имеет множество различных кодов ошибок м. man-страницу). Интерес представляют два из них:

23 -- частичная передача из-за ошибки

24 -- неполная передача из-за исчезновения исходных файлов.

Когда rsync выполняет передачу и сталкивается с одной из этих ситуаций, он не останавливается сразу. Он пропускает их и продолжает передачу файлов, которые он может передать. В конце он выдает код возврата.

Поэтому, если вы получили ошибку 23/24, просто запустите rsync повторно. Последующие запуски будут проходить намного быстрее, обычно просто передавая недостающие файлы из предыдущего запуска. В конце концов вы получите (или должны получить) чистый результат. Что касается атомарности, я использую каталог «tmp» во время переноса. Затем, когда rsync работает чисто, я переименовываю его (атомарно) в <дата>. Я также использую опцию --link-dest, но я использую ее для сохранения дельта-резервных копий (например, --link-dest=yesterday для ежедневных операций).

Хотя я сам его не использовал, параметр --partial-dir=DIR может предотвратить загромождение каталога резервного копирования скрытыми файлами. Убедитесь, что DIR находится в той же файловой системе, что и каталог резервного копирования, чтобы переименования были атомарными. Хотя я делаю это на perl, я написал сценарий, который суммирует то, что я говорил, с немного большей детализацией/точностью для вашей конкретной ситуации. Он написан на tcsh-подобном синтаксисе (непроверенном и немного грубом), но рассматривайте его как псевдокод для написания собственного сценария на bash, perl, python по своему усмотрению. Обратите внимание, что в нем нет ограничения на повторные попытки, но вы можете легко добавить это в соответствии с вашими пожеланиями.

#!/bin/tcsh -f

# repo_backup -- резервное копирование репозиториев, даже если они изменились

# use_tmp -- использовать временную директорию назначения

# use_partial -- использовать частичную директорию

# use_delta -- делать дельта-резервное копирование

# установить имя удаленного сервера ...

set remote_server="..."

# каталог на сервере для резервных копий

set backup_top="/path_to_backup_top"

set backup_backups="$backup_top/backups"

# установите параметры rsync ...

set rsync_opts=(...)

# не допускать загромождения резервной копии частичными файлами

set server_partial=${remote_server}:$backup_top/partial

if ($use_partial) then

    set rsync_opts=($rsync_opts --partial-dir=$server_partial)

endif

# сделать дельта-резервное копирование

if ($use_delta) then

    set latest=(`ssh ${remote_server} ls $backup_backups | tail -1`)

    # получить последнюю версию

    set delta_dir="$backup_backups/$latest"

    if ($#latest > 0) then

        set rsync_opts=($rsync_opts --link-dest=${remote_server}:$delta_dir)

    endif

endif

while (1)

    # получить список всего для резервного копирования

    # установите любой нужный вам параметр

    cd /local_top_directory

    set transfer_list=(.)

    # используйте любой формат по своему усмотрению

    set date=`date +%Y%m%d_%H%M%S`

    set server_tmp=${remote_server}:$backup_top/tmp

    set server_final=${remote_server}:$backup_backups/$date

    if ($use_tmp) then

        set server_transfer=$server_tmp

    else

        set server_transfer=$server_final

    endif

    # выполнить передачу

    rsync $rsync_opts $transfer_list $server_transfer

    set code=$status

    # запуск был чистым

    if ($code == 0) then

        # atomically install backup

        if ($use_tmp) then

            ssh ${remote_server} mv $backup_top/tmp $backup_backups/$date

        endif

        break

    endif

    # partial - некоторая ошибка

    if ($code == 23) then

        continue

    endif

    # partial - некоторые файлы исчезли

    if ($code == 24) then

        continue

    endif

    echo "фатальная ошибка ..."

    exit(1)

end

Ответ 3

Является ли синхронизация зеркал автоматической (задача cron или что-то подобное)? Если да, то вы, вероятно, используете для этого выделенного пользователя ОС, я прав? Тогда решение может быть таким: вместо простого копирования:

  1. Установите разрешения на каталог назначения так, чтобы только rsync мог получить к нему доступ.

  2. Приступите к синхронизации.

  3. Измените разрешения целевого каталога, чтобы остальные могли снова получить к нему доступ.

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

Ответ 4

Не уверен, что это вам поможет, но...

Если вы не против копирования всего набора данных каждый раз и если вы можете использовать симлинки для ссылки на целевой каталог, то вы должны передавать состояние rsync во временный каталог, а затем поменять местами (rename()) старые и новые симлинки атомарно, примерно так:

% mkdir old_data new_data

% ln -s old_data current

% ln -s new_data new

% strace mv -T new current

который запускает:

rename("new", "current") = 0

и выдает:

current -> new_data

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

Схожие статьи

Linux

Как установить Debian 8 и 9 правильно на устройство: с чего начать

Linux

Что мешает кому-то установить некоторую запись для моего домена?

Linux

Поиск файла по содержимому в Linux. Поиск текста в файлах Linux

Linux

Команда df в Linux не показывает правильное свободное пространство после удаления файла