基于rsync和inotify实现远程设备间的文件双向实时同步

rsync常常用来在本地两个目录之间或本地计算机与远程计算机之间同步文件,功能与cp、mv、scp、sftp等命令相似,不同之处在于rsync会先计算源和目的文件(或目录)的差异,然后只传输差异部分的数据,所以当文件(或目录)数比较多,且修改比较频繁,又要使源和目的文件(或目录)保持内容一致(如制作镜像)时,rsync相比其他的文件拷贝和传输工具来说,效率会高很多。

大部分linux发行版和macos都已经安装了rsync,无需另行安装,可以直接使用

$ uname -a
Linux 4.14.0_1-0-0-45 #2 SMP Tue Oct 19 18:27:28 CST 2021 x86_64 x86_64 x86_64 GNU/Linux

$ which rsync
/usr/bin/rsync

$ rsync --version
rsync  version 3.1.2  protocol version 31
Copyright (C) 1996-2015 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
    append, ACLs, xattrs, iconv, symtimes, prealloc

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.

1 rsync的三种使用场景

1.1 本地文件同步

1.1.1 常用命令和参数

使用方法与cp、mv等文件拷贝工具相似,常用使用方式和参数如下所示

# 同步到目标目录下,同步后文件的元数据(日期、权限等)与源文件相同
$ rsync -a test.txt dst-dir/

# 打印同步的详细信息
$ rsync -av test.txt dst-dir/

# 不打印同步信息
$ rsync -aq test.txt dst-dir/

# 断点续传,同时打印同步过程的信息
$ rsync -aP test.txt dst-dir/

# 断点续传,但不打印同步过程的信息
$ rsync -a --partial test.txt dst-dir/

# 压缩传输
$ rsync -az test.txt dst-dir/

# 同步到目标目录下,同时修改名字
$ rsync -a test.txt dst-dir/test2.txt

# 如果目标目录不存在,会创建目录
$ rsync -a test.txt dst-dir/log/

# 同步目录及目录下的所有内容到目标目录
$ rsync -a src-dir dst-dir

# 同步目录下的所有内容到目标目录,不包括目录本身
$ rsync -a src-dir/ dst-dir

# 将目录下的内容全部同步到目标目录,同时删除目标目录下原先存在但不在源目录下的内容
$ rsync -a --delete src-dir/ dst-dir

# 排除部分目录不同步,excluded_dir1和excluded_dir2均是src-dir/目录的相对目录,
# excluded_dir2/*表示只排除excluded_dir2目录下的所有内容,但是会同步excluded_dir2目录本身
$ rsync -a --exclude 'excluded_dir1' --exclude 'excluded_dir2/*' src-dir/ dst-dir/或
$ rsync -a --exclude-from='exclude-file.txt' src-dir/ dst-dir/
$ cat exclude-file.txt
excluded_dir1
excluded_dir2

本人比较常用的参数组合是

$ rsync -azvP src-dir/ dst-dir/

1.1.2 命令参数详细介绍

-v, --verbose          详细模式输出。
-q, --quiet            精简输出模式。
-c, --checksum         打开校验开关,强制对文件传输进行校验。
-a, --archive          归档模式,表示以递归方式传输文件,并保持所有文件属性,等于 -rlptgoD。
-r, --recursive        对子目录以递归模式处理。
-R, --relative         使用相对路径信息。
-b, --backup           创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为 ~filename。可以使用 --suffix 选项来指定不同的备份文件前缀。
--backup-dir           将备份文件(~filename)存放在在目录下。
-suffix=SUFFIX         定义备份文件前缀。
-u, --update           仅仅进行更新,也就是跳过所有已经存在于 DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件。)
-l, --links            保留软链结。
-L, --copy-links       想对待常规文件一样处理软链结。
--copy-unsafe-links    仅仅拷贝指向 SRC 路径目录树以外的链结。
--safe-links           忽略指向 SRC 路径目录树以外的链结。
-H, --hard-links       保留硬链结。
-p, --perms            保持文件权限。
-o, --owner            保持文件属主信息。
-g, --group            保持文件属组信息。
-D, --devices          保持设备文件信息。
-t, --times            保持文件时间信息。
-S, --sparse           对稀疏文件进行特殊处理以节省 DST 的空间。
-n, --dry-run          显示哪些文件将被传输(新增、修改和删除)。
-W, --whole-file       拷贝文件,不进行增量检测。
-x, --one-file-system  不要跨越文件系统边界。
-B, --block-size=SIZE  检验算法使用的块尺寸,默认是 700 字节。
-e, --rsh=COMMAND      指定使用 rsh, ssh 方式进行数据同步。
--rsync-path=PATH      指定远程服务器上的 rsync 命令所在路径信息。
-C, --cvs-exclude      使用和 CVS 一样的方法自动忽略文件,用来排除那些不希望传输的文件。
--existing             仅仅更新那些已经存在于 DST 的文件,而不备份那些新创建的文件。
--delete               删除那些 DST 中 SRC 没有的文件。
--delete-excluded      同样删除接收端那些被该选项指定排除的文件。
--delete-after         传输结束以后再删除。
--ignore-errors        即使出现 IO 错误也进行删除。
--max-delete=NUM       最多删除 NUM 个文件。
--partial              保留那些因故没有完全传输的文件,以便实现断点续传。
--force                强制删除目录,即使不为空。
--numeric-ids          不将数字的用户和组 ID 匹配为用户名和组名。
--timeout=TIME         IP 超时时间,单位为秒。
-I, --ignore-times     不跳过那些有同样的时间和长度的文件。
--size-only            当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间。
--modify-window=NUM    决定文件是否时间相同时使用的时间戳窗口,默认为 0。
-T --temp-dir=DIR      在 DIR 中创建临时文件。
--compare-dest=DIR     同样比较 DIR 中的文件来决定是否需要备份。
--progress             显示传输过程。
-P                     等同于 -partial -progress。
-z, --compress         对备份的文件在传输时进行压缩处理。
--exclude=PATTERN      指定排除不需要传输的文件模式。
--include=PATTERN      指定不排除而需要传输的文件模式。
--exclude-from=FILE    排除 FILE 中指定模式的文件。
--include-from=FILE    不排除 FILE 指定模式匹配的文件。
--version              打印版本信息。
--address              绑定到特定的地址。
--config=FILE          指定其他的配置文件,不使用默认的 rsyncd.conf 文件。
--port=PORT            指定其他的 rsync 服务端口。
--blocking-io          对远程 shell 使用阻塞 IO。
--stats                给出某些文件的传输状态。
--log-format=formAT    指定日志文件格式。
--password-file=FILE   从 FILE 中得到密码。
--bwlimit=KBPS         限制 I/O 带宽,KBytes per second。
-h, --help             显示帮助信息。

1.2 本地与远程间基于SSH进行文件同步

通过ssh协议传输文件,命令格式与scp基本一致,包括ssh key的使用

# 同步本地文件到远程机器
$ rsync -azvP /home/work/local-dir/ remote_user@remote_host_or_ip:/home/work/remote-dir/

# 同步远程机器文件到本地
$ rsync -azvP remote_user@remote_host_or_ip:/home/work/remote-dir/ /home/work/local-dir/

# 指定同步时使用的协议,当ssh协议端口不是默认端口时可以通过这种方式显示指定ssh端口
$ rsync -a -e "ssh -p 2322" /home/work/local-dir/ remote_user@remote_host_or_ip:/home/work/remote-dir/

1.3 本地与远程间基于rsync daemon进行文件同步

1.3.1 基本配置与使用

这种方式rsync作为daemon服务进程启动,其他机器与rsync daemon进程通信进行文件同步

先准备一个rsync daemon的启动配置文件,内容如下:

$ sudo su -
# mkdir -p /etc/rsyncd
# vim /etc/rsyncd/rsyncd.conf

uid = root
gid = root
use chroot = yes
max connections = 1000
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsyncd.lock
log file = /var/log/rsyncd.log

[backup]
path = /home/work/backup
read only = no
auth users = backup
secrets file = /etc/rsyncd/backup.secrets

# vim /etc/rsyncd/backup.secrets
backup:backup123

# chmod 600 /etc/rsyncd/backup.secrets

几点说明:

  1. 配置块中的auth users表示该配置块允许的用户名列表,这些用户只有rsyncd自己认识,不需要在系统用户中存在
  2. 配置块中的secrets file记录该配置块的用户名以及相应的密码列表,格式为:user:password,用户名与密码通过:分隔,每行一个用户名密码对
  3. secrets file的权限必须是600,否则报错

如果通过xinetd启动rsyncd,编写一个rsync的服务文件

# vim /etc/xinetd.d/rsync
service rsync
{
    disable = no
    flags       = IPv4
    socket_type     = stream
    wait            = no
    user            = root
    server          = /usr/bin/rsync
    server_args     = --daemon --config=/etc/rsyncd/rsyncd.conf
    log_on_failure  += USERID
}

然后就可以启动rsyncd了

# chkconfig rsync on
# chkconfig xinetd on
# service xinetd restart

检查rsync已经正常启动

# lsof -i :873
COMMAND   PID USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
xinetd  21850 root    5u  IPv4 1904507818      0t0  TCP *:rsync (LISTEN)

然后就可以基于rsyncd进行文件同步了

$ rsync -avzP /home/work/local-dir/ backup@remote-host-or-ip::backup/remote-dir/ --password-file=backup.secrets
$ rsync -avzP backup@remote-host-or-ip::backup/remote-dir/ /home/work/local-dir/ --password-file=backup.secrets
或
$ rsync -avzP /home/work/local-dir/ rsync://backup@remote-host-or-ip/backup/remote-dir/ --password-file=backup.secrets
$ rsync -avzP rsync://backup@remote-host-or-ip/backup/remote-dir/ /home/work/local-dir/ --password-file=backup.secrets

几点说明:

  1. 远程机器hostname或ip与模块名(rsyncd.conf配置文件中配置的模块)之间通过::符号分隔,表示通过rsyncd同步,以与基于ssh方式进行区分(通过:分隔)。或者,通过rsync://显示指定基于rsync协议进行同步
  2. 模块名、用户、密码与rsyncd中的配置需要对应。通过--password-file=backup.secrets指定用户的密码文件。该文件中只要存储用户的密码即可,不需要存储用户名,这个地方与rsyncd的密码配置文件有点差别。比如,上面rsyncd的backup模块配置了用户backup,密码为backup123,那么此处backup.secrets文件中的内容为backup123,否则会报authronize failed错误。
  3. 密码文件backup.secrets的权限必须为600,否则报错

1.3.2 CentOS 7配置和启动

/usr/lib/systemd/system目录下存在(不存在则自己创建)以下几个rsync的配置文件:

# cd /usr/lib/systemd/system

# cat rsyncd.socket 
[Unit]
Description=Rsync Server Socket
Conflicts=rsyncd.service

[Socket]
ListenStream=873
Accept=yes

[Install]
WantedBy=sockets.target

# cat rsyncd.service 
[Unit]
Description=fast remote file copy program daemon
ConditionPathExists=/etc/rsyncd.conf

[Service]
EnvironmentFile=/etc/sysconfig/rsyncd
ExecStart=/usr/bin/rsync --daemon --no-detach "$OPTIONS"

[Install]
WantedBy=multi-user.target

# cat /etc/sysconfig/rsyncd
OPTIONS="" 

编辑/etc/rsyncd.confsecrets等配置文件内容:

# vim /etc/rsyncd.conf

uid = root
gid = root
use chroot = yes
max connections = 1000
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsyncd.lock
log file = /var/log/rsyncd.log

[backup]
path = /home/work/backup
read only = no
auth users = backup
secrets file = /etc/rsyncd/backup.secrets

# mkdir rsyncd
# vim /etc/rsyncd/backup.secrets
backup:backup123

# chmod 600 /etc/rsyncd/backup.secrets

启动

# systemctl daemon-reload
# systemctl enable --now rsyncd
Created symlink from /etc/systemd/system/multi-user.target.wants/rsyncd.service to /usr/lib/systemd/system/rsyncd.service.
# systemctl status rsyncd
● rsyncd.service - fast remote file copy program daemon
   Loaded: loaded (/usr/lib/systemd/system/rsyncd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2022-12-20 21:32:28 CST; 12s ago
 Main PID: 44780 (rsync)
   CGroup: /system.slice/rsyncd.service
           └─44780 /usr/bin/rsync --daemon --no-detach

Dec 20 21:32:28 yq01-sys-netadmin01.yq01.baidu.com systemd[1]: Started fast remote file copy program daemon.
# lsof -i :873
COMMAND   PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
rsync   44780 root    4u  IPv4 402520293      0t0  TCP *:rsync (LISTEN)
rsync   44780 root    5u  IPv6 402520294      0t0  TCP *:rsync (LISTEN)

1.3.3 rsyncd配置文件详解


######### 全局配置参数 ##########
# 指定rsync端口。默认873
port=873
uid = root
gid = root

# rsync daemon在传输前是否切换到指定的path目录下,并将其监禁在内
use chroot = no
# 指定最大连接数量,0表示没有限制
max connections = 100
# 确保rsync服务器不会永远等待一个崩溃的客户端,0表示永远等待
timeout = 300
# 客户端连接过来显示的消息
motd file = /var/rsyncd/rsync.motd
# 指定rsync daemon的pid文件
pid file = /var/run/rsyncd.pid
# 指定锁文件
lock file = /var/run/rsync.lock
# 指定rsync的日志文件,而不把日志发送给syslog
log file = /var/log/rsyncd.log
# 指定哪些文件不用进行压缩传输
dont compress = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2
# 忽略某些IO错误信息
ignore errors
# 指定连接到该模块的用户列表,只有列表里的用户才能连接到模块,用户名和对应密码保存在secrts file中, 这里使用的不是系统用户,而是虚拟用户。不设置时,默认所有用户都能连接,但使用的是 匿名连接
auth users = rsync
# 保存auth users用户列表的用户名和密码,每行包含一个username:passwd。由于"strict modes" 默认为true,所以此文件要求非rsync daemon用户不可读写。只有启用了auth users该选项才有效
secrets file = /etc/rsync.pass
# 指定该模块是否可读写,即能否上传文件,false表示可读写,true表示可读不可写。所有模块默认不可上传
read only = no
# 指定该模式是否支持下载,设置为true表示客户端不能下载。所有模块默认可下载
#write only = false
# 客户端请求显示模块列表时,该模块是否显示出来,设置为false则该模块为隐藏模块。默认true
list = no
# 指定允许连接到该模块的机器,多个ip用空格隔开或者设置区间
hosts allow = 192.168.10.200
# 指定不允许连接到该模块的机器
hosts deny = 0.0.0.0/32

###########下面指定模块,并设定模块配置参数,可以创建多个模块###########
# 模块ID
[backup]
# 指定该模块的路径,该参数必须指定。启动rsync服务前该目录必须存在。rsync请求访问模块本质就是访问该路径。
path = /backup
comment = backup

2 基于rsync + inotify实现文件实时同步

inotify可以检测文件是否发生了变化,以及发生了哪种类型的变化。这样先通过inotify检测发生变化的文件,然后通过rsync增量同步变化的文件内容,就可以实现文件的实时同步了。

2.1 检查系统是否支持inotify机制

linux内核从2.6.13版本开始就支持inotify机制了,通过以下方式可以检查系统内核是否支持inotify

$ ls -l /proc/sys/fs/inotify/
总用量 0
-rw-r--r-- 1 root root 0 8月  21 11:45 max_queued_events
-rw-r--r-- 1 root root 0 8月  21 11:45 max_user_instances
-rw-r--r-- 1 root root 0 8月   7 22:19 max_user_watches

2.2 inotify安装与应用

2.2.1 下载inotify

下载地址https://sourceforge.net/projects/inotify-tools/files/inotify-tools/3.13/inotify-tools-3.13.tar.gz/download

2.2.2 编译安装

$ sudo su -
# tar -xzf inotify-tools-3.13.tar.gz
# cd inotify-tools-3.13
# ./configure && make && make install

默认安装位置:

# which inotifywait
/usr/local/bin/inotifywait

如果执行inotifywait报找不到共享库,报/usr/local/bin/inotifywait: error while loading shared libraries: libinotifytools.so.0错误,执行下面命令,保证lib64目录下能找到相应的共享库

# ln -sv /usr/local/lib/libinotify* /usr/lib/
# ln -s /usr/local/lib/libinotifytools.so.0 /usr/lib64/libinotifytools.so.0

2.2.3 inotify使用示例

通过以下命令可以持续监听logs目录下文件和目录的变化事件

$ inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib logs/

如果这时打开另一个窗口,在logs目录下新增、修改和删除一个文件

$ cp tt2.py logs/
$ echo "modify" >> logs/tt2.py
$ rm -f logs/tt2.py

可以在原先的监听窗口看到以下打印信息:

$ inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib logs/
CREATE logs/tt2.py
MODIFY logs/tt2.py
MODIFY logs/tt2.py
DELETE logs/tt2.py

2.2.4 参数优化

/proc/sys/fs/inotify/目录下的三个inotify参数值可能太小,可以适当调大一点

# vim /etc/sysctl.conf 

fs.inotify.max_queued_events = 65536
fs.inotify.max_user_instances = 1024
fs.inotify.max_user_watches = 65536

# 生效配置
# sysctl -p 

适当调整系统打开的文件数

# vim /etc/security/limits.conf

*    -    nofile 65536

2.2.5 inotify命令说明

inotifywait命令的主要参数说明:

-m,–monitor:持续监听状态   # 重要参数
-r,–recursive:递归查询目录     # 重要参数
-q,–quiet:只打印监控事件的信息     # 重要参数
–excludei:排除文件或目录时,不区分大小写
-t,–timeout:超时时间
–timefmt:指定时间输出格式  # 重要参数
–format:指定事件输出格式,--format '%Xe %w%f'表示输出事件名,且事件名大写,然后输出空格,然后输出文件目录和文件名       # 重要参数
-e,–event:后面指定删、增、改等事件 # 重要参数

inotifywait命令支持的事件:

access:读取文件或目录内容
modify:修改文件或目录内容
attrib:文件或目录的属性改变
close_write:修改真实文件内容   # 重要参数
close_nowrite:文件或目录关闭,在只读模式打开之后关闭的
close:文件或目录关闭,不管读或是写模式
open:文件或目录被打开
moved_to:文件或目录移动到
moved_from:文件或目录从移动
move:移动文件或目录移动到监视目录  # 重要参数
create:在监视目录下创建文件或目录  # 重要参数
delete:删除监视目录下的文件或目录  # 重要参数
delete_self:文件或目录被删除,目录本身被删除
unmount:卸载文件系统

2.3 将inotify和rsync结合起来实现文件实时同步

有了上面的基础知识,我们就可以编写一个脚本实现文件实时同步了

$ vim realtime-rsync.sh

#!/bin/bash

function info() {
    datetime=`date "+%Y-%m-%d %H:%M:%S |"`
    echo -e "\033[1;94m${datetime} INFO |\033[0m\033[0;94m $@ \033[0m"
}

src=/home/work/src-dir/                 # 需要同步的源路径
dst=backup                             # 目标服务器上 rsync --daemon 发布的模块名称。
rsync_passwd_file=/home/work/backup.secrets            # rsync验证的密码文件
dst_ip=10.131.21.21                 # 目标服务器
user=backup                           # rsync --daemon定义的验证用户名


while true
do
    cd ${src}                              
    /usr/local/bin/inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib,close_write,move ./ | while TMOUT="" read line
    do
            INO_EVENT=$(echo $line | awk '{print $1}')      # 把inotify输出切割 把事件类型部分赋值给INO_EVENT
            INO_FILE=$(echo $line | awk '{print $2}')       # 把inotify输出切割 把文件路径部分赋值给INO_FILE
            
            file_name=${INO_FILE##*/}
            # 不同步隐藏文件
            if [[ ${file_name} != .* ]]
            then
                #增加、修改、写入完成、移动进事件
                #增、改放在同一个判断,因为他们都肯定是针对文件的操作,即使是新建目录,要同步的也只是一个空目录,不会影响速度。
                if [[ $INO_EVENT =~ 'CREATE' ]] || [[ $INO_EVENT =~ 'MODIFY' ]] || [[ $INO_EVENT =~ 'CLOSE_WRITE' ]] || [[ $INO_EVENT =~ 'MOVED_TO' ]]         # 判断事件类型
                then
                        info 'rsync $line'
                        rsync -azcR --password-file=${rsync_passwd_file} $(dirname ${INO_FILE}) ${user}@${dst_ip}::${dst}
                fi
                #删除、移动出事件
                if [[ $INO_EVENT =~ 'DELETE' ]] || [[ $INO_EVENT =~ 'MOVED_FROM' ]]
                then
                        info 'rsync $line'
                        rsync -azcR --delete --password-file=${rsync_passwd_file} $(dirname ${INO_FILE}) ${user}@${dst_ip}::${dst}
                        #看rsync命令 如果直接同步已删除的路径${INO_FILE}会报no such or directory错误 所以这里同步的源是被删文件或目录的上一级路径,并加上--delete来删除目标上有而源中没有的文件,这里不能做到指定文件删除,如果删除的路径越靠近根,则同步的目录月多,同步删除的操作就越花时间。这里有更好方法的同学,欢迎交流。
                fi
                #修改属性事件 指 touch chgrp chmod chown等操作
                if [[ $INO_EVENT =~ 'ATTRIB' ]]
                then
                        info 'rsync $line'
                        if [ ! -d "$INO_FILE" ]                 # 如果修改属性的是目录 则不同步,因为同步目录会发生递归扫描,等此目录下的文件发生同步时,rsync会顺带更新此目录。
                        then
                                rsync -azcR --password-file=${rsync_passwd_file} $(dirname ${INO_FILE}) ${user}@${dst_ip}::${dst}
                        fi
                fi
            fi
    done
    
    sleep 3
done
$ nohup ./realtime-rsync.sh 1>realtime-rsync.log 2>&1 &

3 参考

  1. rsync算法原理及使用
  2. rsync + inotify 实现文件实时双向自动同步
  3. rsync inotify 双向同步(已验证成功)
  4. 真正的inotify+rsync实时同步 彻底告别同步慢

你可能感兴趣的:(基于rsync和inotify实现远程设备间的文件双向实时同步)