inotify 与 rsync文件同步实现与问题
首先分别介绍inotify 与 rsync的使用,然后用两者实现实时文件同步,最后说一下这样的系统存在什么样的问题。
1. inotify
这个具体使用网上很多,参考
Inotify 内核版本支持
- 从 kernel 2.6.13 开始, Inotify 正式并入内核,RHEL5 已经支持.
- 看看是否有 /proc/sys/fs/inotify / 目录,以确定内核是否支持
[root@RHEL5 Rsync]# ls -l /proc/sys/fs/inotify/ total 0 -rw-r--r-- 1 root root 0 Oct 9 09:36 max_queued_events -rw-r--r-- 1 root root 0 Oct 9 09:36 max_user_instances -rw-r--r-- 1 root root 0 Oct 9 09:36 max_user_watches
/proc/sys/fs/inotify/max_queued_events 默认值: 16384 该文件中的值为调用 inotify_init 时分配给 inotify instance 中可排队的 event 的数目的最大值,超出这个值得事件被丢弃,但会触发 IN_Q_OVERFLOW 事件
/proc/sys/fs/inotify/max_user_instances 默认值: 128 指定了每一个 real user ID 可创建的 inotify instatnces 的数量上限
/proc/sys/fs/inotify/max_user_watches 默认值: 8192 指定了每个 inotify instance 相关联的 watches 的上限
注意: max_queued_events 是 Inotify 管理的队列的最大长度,文件系统变化越频繁,这个值就应该越大
如果你在日志中看到 Event Queue Overflow,说明 max_queued_events 太小需要调整参数后再次使用.
实例:
inotifywait -mrq --timefmt '%y%m%d %H:%M' --format '%T %e %w%f' -o log.txt -e modify,create,delete /data
参数说明
语法:
inotifywait [-hcmrq] [-e ] [-t ] [--format ] [--timefmt ] [ ... ]
参数:
-h,–help
输出帮助信息
@
排除不需要监视的文件,可以是相对路径,也可以是绝对路径。
–fromfile
从文件读取需要监视的文件或排除的文件,一个文件一行,排除的文件以 @开头。
-m, –monitor
接收到一个事情而不退出,无限期地执行。默认的行为是接收到一个事情后立即退出。
-d, –daemon
跟–monitor 一样,除了是在后台运行,需要指定–outfile 把事情输出到一个文件。也意味着使用了–syslog。
-o, –outfile
输出事情到一个文件而不是标准输出。
-s, –syslog
输出错误信息到系统日志
-r, –recursive
监视一个目录下的所有子目录。
-q, –quiet
指定一次,不会输出详细信息,指定二次,除了致命错误,不会输出任何信息。
–exclude
正则匹配需要排除的文件,大小写敏感。
–excludei
正则匹配需要排除的文件,忽略大小写。
-t , –timeout
设置超时时间,如果为 0,则无限期地执行下去。
-e , –event
指定监视的事件。
-c, –csv
输出 csv 格式。
–timefmt
指定时间格式,用于–format 选项中的 %T 格式。
–format
指定输出格式。
%w 表示发生事件的目录
%f 表示发生事件的文件
%e 表示发生的事件
%Xe 事件以 “X” 分隔
%T 使用由–timefmt 定义的时间格式
可监听事件
access | 文件读取 |
modify | 文件更改。 |
attrib | 文件属性更改,如权限,时间戳等。 |
close_write | 以可写模式打开的文件被关闭,不代表此文件一定已经写入数据。 |
close_nowrite | 以只读模式打开的文件被关闭。 |
close | 文件被关闭,不管它是如何打开的。 |
open | 文件打开。 |
moved_to | 一个文件或目录移动到监听的目录,即使是在同一目录内移动,此事件也触发。 |
moved_from | 一个文件或目录移出监听的目录,即使是在同一目录内移动,此事件也触发。 |
move | 包括 moved_to 和 moved_from |
move_self | 文件或目录被移除,之后不再监听此文件或目录。 |
create | 文件或目录创建 |
delete | 文件或目录删除 |
delete_self | 文件或目录移除,之后不再监听此文件或目录 |
unmount | 文件系统取消挂载,之后不再监听此文件系统。 |
2.rsync
实例
rsync -azcvR /data /backup
具体各参数的含义参考链接文件
3.inotify+rsync 同步
参考 真正的 inotify+rsync 实时同步 彻底告别同步慢
代码
#!/bin/bash /usr/bin/inotifywait -mrq --format '%w%f'-e create,close_write,delete /backup |while read file #把发生更改的文件列表都接收到 file 然后循环, 下面做的是全量 rsync do cd /backup && rsync -az --delete /backup/ rsync_backup@192.168.24.101::backup/--password-file=/etc/rsync.password done
这个程序有个问题,就是每当inotify检测到有事件发生,都进行同步一次,太麻烦
优化:
#!/bin/bash src=/home/dahu/Homework/daqing # 需要同步的源路径 des=/home/dahu/Homework/daqing.bk # 目标服务器上 cd ${src} # 此方法中,由于 rsync 同步的特性,这里必须要先 cd 到源目录,inotify 再监听 ./ 才能 rsync 同步后目录结构一致,有兴趣的同学可以进行各种尝试观看其效果 function myfunc { #检测父文件夹是否存在 if [ -d $(dirname $1) ] then echo "$(dirname $1)" else dir=`dirname $1` myfunc $dir fi } true && { inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib,close_write,move ./ | while read file # 把监控到有发生更改的 "文件路径列表" 循环 do INO_EVENT=$(echo $file | awk '{print $1}') # 获取事件 INO_FILE=$(echo $file | awk '{print $2}') # 获取文件 echo "-------------------------------$(date)------------------------------------" echo $file #增加、写入完成、移动进事件 if [[ $INO_EVENT =~ 'CREATE' ]] || [[ $INO_EVENT =~ 'CLOSE_WRITE' ]] || [[ $INO_EVENT =~ 'MOVED_TO' ]] then echo 'CREATE or CLOSE_WRITE or MOVED_TO' rsync -avzcuR $(dirname ${INO_FILE}) ${des} #仅仅更新新的东西,需要加个u,也就是跳过所有已经存在于 DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件) fi #修改事件 if [[ $INO_EVENT =~ 'MODIFY' ]] then echo 'MODIFY' rsync -avzcR ${INO_FILE} ${des} #修改文件,只需要同步该文件即可 fi #删除、移动出事件 if [[ $INO_EVENT =~ 'DELETE' ]] || [[ $INO_EVENT =~ 'MOVED_FROM' ]] then echo 'DELETE or MOVED_FROM' rsync -avzuR --delete $(myfunc ${INO_FILE}) ${des} #看 rsync 命令 如果直接同步已删除的路径 ${INO_FILE} 会报 no such or directory 错误 所以这里同步的源是被删文件或目录的上一级路径,并加上 --delete 来删除目标上有而源中没有的文件,这里不能做到指定文件删除,如果删除的路径越靠近根,则同步的目录月多,同步删除的操作就越花时间。这里有更好方法的同学,欢迎交流。 fi #修改属性事件 指 touch chgrp chmod chown 等操作 if [[ $INO_EVENT =~ 'ATTRIB' ]] then echo 'ATTRIB' if [ ! -d "$INO_FILE" ] # 如果修改属性的是目录 则不同步,因为同步目录会发生递归扫描,等此目录下的文件发生同步时,rsync 会顺带更新此目录。 then rsync -avzcuR ${INO_FILE} ${des} fi fi done }
优化思路就是,把每次不同类型的事件,做一个分解:
修改文件的,只同步该文件即可;
增加文件或文件夹的,更新文件夹目录,注意这里的更新,是指 rsync -u 仅仅进行更新,也就是跳过所有已经存在于 DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件),如果你修改了原文件的内容,但不改变名字,就意味着不同步到DST,所以先把修改内容的情况考虑了
删除文件时,这个时候你再同步,即使使用rsync --delete, 删除那些 DST 中 SRC 没有的文件,但是这个文件夹已经删除了,所以得需要访问他存在的父文件夹,不然会报错
即使这3个优化过了,但还是会慢,为什么?
遇到大文件的情况,或者是简单的用vim 生成文件,这个时候inotify 会监控到很多中间文件(如vim的swp),大文件的各种modify操作,都会监控到,这个时候再用刚刚那个程序,每次监控到一个事件,(即使是优化过了,只值针对该文件,不优化的话要同步所有的),都要同步一次,麻烦且耗时。
假设你生成1G的文件,会监控到100次的modify事件,即同步100次,那就是rsync 100*1G,当然使用-u这个参数会好一点,但是如果是修改大文件又保存呢?想象一下就知道需要不停的同步,况且,必须得等到这些前面的循环都跑完了再跑后面的,这个同步的时间不能做到很及时。 刚刚又实验了一下,7.4M的文件,需要419次
4.自己改进
优化的思路来源于第三点的疑问,我没必要同步那么多次(代码中的-u),在这里我在单位时间(2s)内,先把所有操作去重(针对大文件),把modify和别的操作区分对待,对modify的操作同步对应文件,其余的更新父文件夹,记得加-u 和--delete即可。应该算是解决了第3点提出的疑问,重复次数不会很多,暂时测试效果还可以。
cat tt.sh #!/bin/bash src=/home/dahu/Homework/daqing # 需要同步的源路径 des=/home/dahu/Homework/daqing.bk # 目标服务器 cd `dirname $src` rm -rf log* inotifywait -mrq --format '%Xe %w%f' -e modify,create,delete,attrib,close_write,move `basename $src` > log & function testdir { #显示存在的最深的文件夹 #echo 'hehe' if [ -d $(dirname $1) ] then echo "$(dirname $1)" else dir=`dirname $1` testdir $dir fi } #myfunc daqing/p/q/r/s/t #2s一次循环 cyclenum=`wc -l log |awk '{print $1+1}'` while true do lastnum=$cyclenum sleep 1 sec=`date +%S` if [ `expr $sec % 2` = "0" ] then #date #这种方法是失效的,why,因为> ,文件删除或者move,就不会再继续往里添加文件了,所以按指定行数来显示比较靠谱 #cat log > log.2 #md1=`md5sum log|awk '{print $1}'` && md2=`md5sum log.2|awk '{print $1}'` #if [ $md1 != $md2 ] ;then echo "md5 not equal" && continue ;fi #touch log.1 && mv log.1 log && cat log.2 >> log.all cyclenum=`wc -l log |awk '{print $1+1}'` #sed -n '94,2p' sed -n "${lastnum},${cyclenum}p" log > log.2 #这一轮的操作, 暂时证明这个方法是可用的 #sed -n "${lastnum},${cyclenum}p" log >> log.all #用来检测是否漏掉监控的操作 sort -u log.2 > log.uniq for line in `grep "MODIFY" log.uniq|awk '{print $2}'` do #echo $line if [ -f $line ] ;then rsync -avzcR $line $des;fi done for line in `awk '{print $2}' log.uniq` do dir=`testdir $line` rsync -avzuR --delete $dir $des done fi done