NAS 中备份可以使用rsync,鲁棒又可靠,结合 inotify 可以动态实时同步,本文记录相关方法。
根据 inotify 的相关知识,可以发现,很多动作都涉及了close事件,且大多数情况都是伴随着close_write事件的。所以,大多数情况下在定义监控事件时,其实并不真的需要监控open、modify、close事件。特别是close,只需监控它的分支事件close_write和close_nowrite即可。由于一般情况下inotify都是为了监控文件的增删改,不会监控它的访问,所以一般只需监控close_write即可。
delete,close_write,moved_to,moved_from,isdir
事件# cat a.sh
#!/bin/bash
#
inotifywait -mrq -e delete,close_write,moved_to,moved_from,isdir /longshuai |\
while read line;do
if echo $line | grep -i delete &>/dev/null; then
echo "At `date +"%F %T"`: $line" >>/etc/delete.log
else
rsync -az $line --password-file=/etc/rsync_back.passwd rsync://rsync_backup@172.16.10.6::longshuai
fi
done
触发事件后逐行进行同步。该脚本中已经尽量少地设置监控事件,使得它尽量少重复触发rsync。但需要明确的是,尽管设计的目标是尽量少触发事件,但应该以满足需求为前提来定义监控事件。如果不清楚如何选择监控事件,回看前文inotify命令以及事件分析。另外,可以考虑对文件、目录、子目录单独定义不同的脚本分别监控不同事件。
该脚本的不足之处主要在于重复触发rsync。该脚本中rsync同步的是目录而非单个文件,所以如果一次性操作了该目录中多个文件,将会产生多个事件,也因此会触发多次rsync命令,在前文中给出了一个拷贝/usr/share/man的示例,它调用了15000多次rsync,其实只需同步一次即可,剩余的上万次同步完全是多余的。
[root@xuexi www]# cat ~/inotify.sh
#!/bin/bash
watch_dir=/www
push_to=172.16.10.5
inotifywait -mrq -e delete,close_write,moved_to,moved_from,isdir --timefmt '%Y-%m-%d %H:%M:%S' --format '%w%f:%e:%T' $watch_dir \
--exclude=".*.swp" |\
while read line;do
# logging some files which has been deleted and moved out
if echo $line | grep -i -E "delete|moved_from" &>/dev/null;then
echo "$line" >> /etc/inotify_away.log
fi
# from here, start rsync's function
rsync -az --delete --exclude="*.swp" --exclude="*.swx" $watch_dir $push_to:/tmp
if [ $? -eq 0 ];then
echo "sent $watch_dir success"
else
echo "sent $watch_dir failed"
fi
done
注意,该脚本是用来前台测试运行的,如果要后台运行,则将最后一段的if字句删掉。
该脚本记录了哪些被删除或从监控目录中移出的文件,且监控到事件后,触发的rsync操作是对整个监控目录$watch_dir进行同步,并且不对vim产生的临时文件进行同步。同时该脚本会产生多余的资源消耗。
每触发一次事件会同步所有数据,会造成巨大的资源消耗。
虽然inotify已经整合到了内核中,在应用层面上也常拿来辅助rsync实现实时同步功能,但是inotify因其设计太过细致从而使得它配合rsync并不完美,所以需要尽可能地改进inotify+rsync脚本。另外,inotify存在bug。
当向监控目录下拷贝复杂层次目录(多层次目录中包含文件),或者向其中拷贝大量文件时,inotify经常会随机性地遗漏某些文件。这些遗漏掉的文件由于未被监控到,所有监控的后续操作都不会执行,例如不会被rsync同步。
实际上,上面描述的问题不是inotify的缺陷,而是inotify-tools包中inotifywait工具的缺陷。inotifywait的man文档中也给出了这个bug说明。
BUGS
There are race conditions in the recursive directory watching code which can cause events to be missed if they occur in a directory immediately after that directory is created. This is probably not fixable.
也就是说,那些直接发起inotify相关系统调用的上层工具(如sersync、lsyncd等)可能不会出现这个bug。
由于inotify的bug,使用inotify+rsync时应该总是让rsync同步目录,而不是同步那些产生事件的单个文件,否则很可能会出现文件遗漏。另一方面,同步单个文件的性能非常差。
使用inotify+rsync时,考虑两方面问题:
由于这两个缺陷,使得通过脚本实现的inotify+rsync几乎很难达到完美,即使要达到不错的完美度,也不是件容易的事。
在设计inotify+rsync脚本过程中,有以下几个目标应该尽量纳入考虑或达到:
在上面已经提过 inotify + rsync 不足之处以及改进的目标。以下是通过修改shell脚本来改进inotify+rsync的示例。
[root@xuexi tmp]# cat ~/inotify.sh
#!/bin/bash
watch_dir=/www
push_to=172.16.10.5
# First to do is initial sync
rsync -az --delete --exclude="*.swp" --exclude="*.swx" $watch_dir $push_to:/tmp
inotifywait -mrq -e delete,close_write,moved_to,moved_from,isdir --timefmt '%Y-%m-%d %H:%M:%S' --format '%w%f:%e:%T' $watch_dir \
--exclude=".*.swp" >>/etc/inotifywait.log &
while true;do
if [ -s "/etc/inotifywait.log" ];then
grep -i -E "delete|moved_from" /etc/inotifywait.log >> /etc/inotify_away.log
rsync -az --delete --exclude="*.swp" --exclude="*.swx" $watch_dir $push_to:/tmp
if [ $? -ne 0 ];then
echo "$watch_dir sync to $push_to failed at `date +"%F %T"`,please check it by manual" |\
mail -s "inotify+Rsync error has occurred" root@localhost
fi
cat /dev/null > /etc/inotifywait.log
rsync -az --delete --exclude="*.swp" --exclude="*.swx" $watch_dir $push_to:/tmp
else
sleep 1
fi
done
另外,脚本中inotifywait命令中的后台符号"&"绝不能少,否则脚本将一直处于inotifywait命令阶段,不会进入到下一步的循环阶段。但需要注意,脚本中(子shell)的后台进程在脚本结束的时候不会随之停止,而是挂靠在pid=1的init/systemd进程下,这种情况下可以直接使用 killall script_file 的方式来停止脚本,这样脚本中的后台也会中断。