Rsync + inotify 做文件实时同步
此前的文章中,我介绍了 rsync 的工作原理及使用。而在此前的使用中,我是通过命令行的方式执行 rsync 客户端命令来同步数据的,在实际的应用中,更多的是配置定时任务,定时的执行客户端命令去同步数据。
一般而言,rsync 客户端命令大致都是这样:
rsync -avrz /data remote_host::data
意思是把本地 /data 目录推送到远端 rsync 服务的 data 模块对应的 path 目录中。然后,把这条命令添加到定时任务中去,就达到了定时同步的目的了。
虽然这样能实现我们的需求,但是当 /data 目录下有几千、几万、几十万甚至更多文件时,rsync 会遍历 /data 的所有文件,每一个文件都要计算摘要和校验和。这绝对是灾难性的,会损耗服务器的大量资源来处理同步的事情。
那么,我们就想,有没有一种机制,能实现当文件发生改变时才触发同步到远端服务器呢?答案是有的,它就是 inotify。
inotify 介绍
众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,如 hotplug、udev 等。inotify 就是这种需求催生的。Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理,udev 动态地维护 /dev 下的设备文件,inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知,该机制是著名的桌面搜索引擎项目 beagle 引入的,并在 Gamin 等项目中被应用。
事实上,在 inotify 之前已经存在一种类似的机制叫 dnotify,但是它存在许多缺陷,inotify 是为替代 dnotify 而设计的,它克服了 dnotify 的缺陷,提供了更好用的,简洁而强大的文件变化通知机制:
- inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
- inotify 既可以监视文件,也可以监视目录。
- inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
- inotify 使用文件描述符作为接口,因而可以使用通常的文件 I/O 操作select 和 poll 来监视文件系统的变化。
inotify 可以监视的文件系统事件包括:
- IN_ACCESS,即文件被访问
- IN_MODIFY,文件被 write
- IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等
- IN_CLOSE_WRITE,可写文件被 close
- IN_CLOSE_NOWRITE,不可写文件被 close
- IN_OPEN,文件被 open
- IN_MOVED_FROM,文件被移走,如 mv
- IN_MOVED_TO,文件被移来,如 mv、cp
- IN_CREATE,创建新文件
- IN_DELETE,文件被删除,如 rm
- IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己
- IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己
- IN_UNMOUNT,宿主文件系统被 umount
- IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
- IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)
inotify-tools
前面说到,inotify 是 Linux 内核提供的文件状态监控、通知机制,准确来说,Linux内核从 2.6.13 开始引入的。而我们作为使用者,是无法和内核打交道的。
inotify-tools 是为 linux 下 inotify 文件监控工具提供的一套 C 的开发接口库函数,同时还提供了一系列的命令行工具,这些工具可以用来监控文件系统的事件。 inotify-tools 是用 C 编写的,除了要求内核支持 inotify 外,不依赖于其他。
inotify-tools 提供两个命令行工具:
inotifywait
:它是用来监控文件或目录的变化inotifywatch
:它是用来统计文件系统访问的次数
inotifywait使用示例
监控/root/tmp目录文件的变化:
/usr/bin/inotifywait -mrq --timefmt '%Y/%m/%d-%H:%M:%S' --format '%T %w %e %f' \
-e modify,delete,create,move,attrib /root/tmp/
上面的命令表示,持续监听/root/tmp
目录及其子目录的文件变化,监听事件包括文件被修改、删除、创建、移动、属性更改,显示到屏幕。显示的格式为'%T %w %e %f',即时间、目录、事件类型、文件。执行完上面的命令后,在/root/tmp
下创建或修改文件都会有信息输出:
2018/11/16-18:02:24 /root/tmp/ CREATE .test.swp
2018/11/16-18:02:24 /root/tmp/ CREATE .test.swpx
2018/11/16-18:02:24 /root/tmp/ DELETE .test.swpx
2018/11/16-18:02:24 /root/tmp/ DELETE .test.swp
2018/11/16-18:02:24 /root/tmp/ CREATE .test.swp
2018/11/16-18:02:24 /root/tmp/ MODIFY .test.swp
2018/11/16-18:02:24 /root/tmp/ ATTRIB .test.swp
2018/11/16-18:02:29 /root/tmp/ MODIFY .test.swp
2018/11/16-18:02:33 /root/tmp/ MODIFY .test.swp
2018/11/16-18:02:38 /root/tmp/ CREATE 4913
2018/11/16-18:02:38 /root/tmp/ ATTRIB 4913
2018/11/16-18:02:38 /root/tmp/ DELETE 4913
2018/11/16-18:02:38 /root/tmp/ MOVED_FROM test
2018/11/16-18:02:38 /root/tmp/ MOVED_TO test~
2018/11/16-18:02:38 /root/tmp/ CREATE test
2018/11/16-18:02:38 /root/tmp/ ATTRIB test
2018/11/16-18:02:38 /root/tmp/ MODIFY .test.swp
2018/11/16-18:02:38 /root/tmp/ DELETE test~
2018/11/16-18:02:38 /root/tmp/ DELETE .test.swp
rsync + inotify-tools 实现实时同步
这一步的核心其实就是在客户端创建一个脚本rsync.sh
,适用inotifywait
监控本地目录的变化,触发rsync
将变化的文件传输到远程备份服务器上。为了更接近实战,我们要求一部分子目录不同步,如/root/tmp/log
和临时文件。
创建排除在外不同步的文件列表
排除不需要同步的文件或目录有两种做法,第一种是inotify监控整个目录,在rsync中加入排除选项,简单;第二种是inotify排除部分不监控的目录,同时rsync中也要加入排除选项,可以减少不必要的网络带宽和CPU消耗。我们选择第二种。
inotifywait排除
这个操作在客户端进行,假设/tmp/src/mail/2014/
以及/tmp/src/mail/2015/cache/
目录下的所有文件不用同步,所以不需要监控,/tmp/src/
下的其他文件和目录都同步。(其实对于打开的临时文件,可以不监听modify
时间而改成监听close_write
)
inotifywait排除监控目录有--exclude <pattern>
和--fromfile <file>
两种格式,并且可以同时使用,但主要前者可以用正则,而后者只能是具体的目录或文件。
vim /etc/inotify_exclude.lst
# 内容
/tmp/src/pdf
@/tmp/src/2014
使用fromfile
格式只能用绝对路径,不能使用诸如*
正则表达式去匹配,@
表示排除。
如果要排除的格式比较复杂,必须使用正则,那只能在inotifywait
中加入选项,如--exclude '(.*/*\.log|.*/*\.swp)$|^/tmp/src/mail/(2014|201.*/cache.*)'
,表示排除/tmp/src/mail/以下的2014目录,和所有201*目录下的带cache的文件或目录,以及/tmp/src目录下所有的以.log或.swp结尾的文件。
rsync排除
使用inotifywait排除监控目录的情况下,必须同时使用rsync排除对应的目录,否则只要有触发同步操作,必然会导致不该同步的目录也会同步。与inotifywait类似,rsync的同步也有--exclude
和--exclude-from
两种写法。
使用--include-from=FILE
时,排除文件列表要用绝对路径,但FILE里面的内容要用相对路径,如:
mail/2014/
mail/201*/201*/201*/.??*
mail??*
src/*.html*
src/js/
src/ext3/
src/2014/20140[1-9]/
src/201*/201*/201*/.??*
membermail/
membermail??*
membermail/201*/201*/201*/.??*
排除同步的内容包括,mail下的2014目录,类似2015/201501/20150101/下的临时或隐藏文件,等。
同步脚本rsync.sh
#rsync auto sync script with inotify
#2014-12-11 Sean
#variables
current_date=$(date +%Y%m%d_%H%M%S)
source_path=/tmp/src/
log_file=/var/log/rsync_client.log
#rsync
rsync_server=172.29.88.223
rsync_user=sean
rsync_pwd=/etc/rsync_client.pwd
rsync_module=module_test
INOTIFY_EXCLUDE='(.*/*\.log|.*/*\.swp)$|^/tmp/src/mail/(2014|20.*/.*che.*)'
RSYNC_EXCLUDE='/etc/rsyncd.d/rsync_exclude.lst'
#rsync client pwd check
if [ ! -e ${rsync_pwd} ];then
echo -e "rsync client passwod file ${rsync_pwd} does not exist!"
exit 0
fi
#inotify_function
inotify_fun(){
/usr/bin/inotifywait -mrq --timefmt '%Y/%m/%d-%H:%M:%S' --format '%T %w %f' \
--exclude ${INOTIFY_EXCLUDE} -e modify,delete,create,move,attrib ${source_path} \
| while read file
do
/usr/bin/rsync -auvrtzopgP --exclude-from=${RSYNC_EXCLUDE} --progress --bwlimit=200 --password-file=${rsync_pwd} ${source_path} ${rsync_user}@${rsync_server}::${rsync_module}
done
}
#inotify log
inotify_fun >> ${log_file} 2>&1 &