inotify 与 rsync文件同步实现与问题

时间:2024-10-27 21:04:26

首先分别介绍inotify 与 rsync的使用,然后用两者实现实时文件同步,最后说一下这样的系统存在什么样的问题。

1. inotify

这个具体使用网上很多,参考

inotify-tools 命令使用讲解

Inotify 内核版本支持

  • 从 kernel 2.6.13 开始, Inotify 正式并入内核,RHEL5 已经支持.
  • 看看是否有 /proc/sys/fs/inotify / 目录,以确定内核是否支持
 [root@RHEL5 Rsync]# ls -l /proc/sys/fs/inotify/
total
-rw-r--r-- root root Oct : max_queued_events
-rw-r--r-- root root Oct : max_user_instances
-rw-r--r-- root root Oct : 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

参考 Linux 下 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 $) ]
then
echo "$(dirname $1)"
else
dir=`dirname $`
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 $) ]
then
echo "$(dirname $1)"
else
dir=`dirname $`
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
sec=`date +%S`
if [ `expr $sec % ` = "" ]
then
#date
#这种方法是失效的,why,因为> ,文件删除或者move,就不会再继续往里添加文件了,所以按指定行数来显示比较靠谱
#cat log > log.
#md1=`md5sum log|awk '{print $1}'` && md2=`md5sum log.|awk '{print $1}'`
#if [ $md1 != $md2 ] ;then echo "md5 not equal" && continue ;fi
#touch log. && mv log. log && cat log. >> log.all
cyclenum=`wc -l log |awk '{print $1+1}'`
#sed -n '94,2p'
sed -n "${lastnum},${cyclenum}p" log > log. #这一轮的操作, 暂时证明这个方法是可用的
#sed -n "${lastnum},${cyclenum}p" log >> log.all #用来检测是否漏掉监控的操作
sort -u log. > 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