前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux 结合 inotify 和 rsync 实现文件动态同步

Linux 结合 inotify 和 rsync 实现文件动态同步

作者头像
为为为什么
发布2022-08-06 14:43:50
1.9K0
发布2022-08-06 14:43:50
举报
文章被收录于专栏:又见苍岚

NAS 中备份可以使用rsync,鲁棒又可靠,结合 inotify 可以动态实时同步,本文记录相关方法。

基础知识

文件动态同步

根据 inotify 的相关知识,可以发现,很多动作都涉及了close事件,且大多数情况都是伴随着close_write事件的。所以,大多数情况下在定义监控事件时,其实并不真的需要监控open、modify、close事件。特别是close,只需监控它的分支事件close_write和close_nowrite即可。由于一般情况下inotify都是为了监控文件的增删改,不会监控它的访问,所以一般只需监控close_write即可。

  • 建议对监控对象的close_write、moved_to、moved_from、delete和isdir(主要是create,isdir,但无法定义这两个事件的整体,所以仅监控isdir)事件定义对应的操作,因为它们互不重复。如有需要,可以将它们分开定义,再添加需要监控的其他事件。
示例脚本
  • 基础同步脚本,监控文件夹下的 delete,close_write,moved_to,moved_from,isdir 事件
代码语言:javascript
复制
# 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,其实只需同步一次即可,剩余的上万次同步完全是多余的。

  • 网络同步脚本
代码语言:javascript
复制
[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 不足之处

虽然inotify已经整合到了内核中,在应用层面上也常拿来辅助rsync实现实时同步功能,但是inotify因其设计太过细致从而使得它配合rsync并不完美,所以需要尽可能地改进inotify+rsync脚本。另外,inotify存在bug。

inotify 的 bug

当向监控目录下拷贝复杂层次目录(多层次目录中包含文件),或者向其中拷贝大量文件时,inotify经常会随机性地遗漏某些文件。这些遗漏掉的文件由于未被监控到,所有监控的后续操作都不会执行,例如不会被rsync同步。

实际上,上面描述的问题不是inotify的缺陷,而是inotify-tools包中inotifywait工具的缺陷。inotifywait的man文档中也给出了这个bug说明。

代码语言:javascript
复制
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+rsync的缺陷
  • inotify 的一个常用的应用为触发文件同步,而由于inotify存在缺陷,导致这种组合使用的策略存在风险

由于inotify的bug,使用inotify+rsync时应该总是让rsync同步目录,而不是同步那些产生事件的单个文件,否则很可能会出现文件遗漏。另一方面,同步单个文件的性能非常差。

使用inotify+rsync时,考虑两方面问题:

  1. 由于inotify监控经常会对一个文件产生多个事件,且一次性操作同一个目录下多个文件也会产生多个事件,这使得inotify几乎总是多次触发rsync同步目录,由于rsync同步的是目录,所以多次触发rsync完全没必要,这会浪费资源和网络带宽;如果是分层次独立监控子目录,则会导致同步无法保证实时性
  2. vim编辑文件的过程中会产生.swp和.swx等临时文件,inotify也会监控这些临时文件,且临时文件会涉及多个事件,因此它们可能也会被rsync拷贝走,除非设置好排除临时文件,但无论如何,这些临时文件是不应该被同步的,极端情况下,同步vim的临时文件到服务器上可能是致命的。

由于这两个缺陷,使得通过脚本实现的inotify+rsync几乎很难达到完美,即使要达到不错的完美度,也不是件容易的事。

  • 因此,为了让inotify+rsync即能保证同步性能,又能保证不同步临时文件,认真设计inotify+rsync的监控事件、循环以及rsync命令是很有必要的。

在设计inotify+rsync脚本过程中,有以下几个目标应该尽量纳入考虑或达到:

  1. 每个文件都尽量少地产生监控事件,但又不能遗漏事件。
  2. 让rsync同步目录,而不是同步产生事件的单个文件。
  3. 一次性操作同步目录下的多个文件会产生多个事件,导致多次触发rsync。如果能让这一批操作只触发一次rsync,则会大幅降低资源的消耗。
  4. rsync同步目录时,考虑好是否要排除某些文件,是否要加上"–delete"选项等。
  5. 为了性能,可以考虑对子目录、对不同事件单独设计inotify+rsync脚本。

inotify+rsync 的最佳实现

在上面已经提过 inotify + rsync 不足之处以及改进的目标。以下是通过修改shell脚本来改进inotify+rsync的示例。

代码语言:javascript
复制
[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

  • 为了让一次性对目录下多个文件的操作只触发一次rsync,通过while read line这种读取标准输入的循环方式是不可能实现的。
  • 该方法是将inotifywait得到的事件记录到文件/etc/inotifywait.log中,然后在死循环中判断该文件,如果该文件不为空则调用一次rsync进行同步,同步完后立即清空inotifywait.log文件,防止重复调用rsync。
  • 但需要考虑一种情况,inotifywait可能会不断地向inotifywait.log中写入数据,清空该文件可能会使得在rsync同步过程中被inotifywait监控到的文件被rsync遗漏,所以在清空该文件后应该再调用一次rsync进行同步,这也变相地实现了失败重传的错误处理功能。
  • 如果没有监控到事件,inotifywait.log将是空文件,此时循环将睡眠1秒钟,所以该脚本并不是百分百的实时,但1秒钟的误差对于cpu消耗来说是很值得的。

另外,脚本中inotifywait命令中的后台符号"&"绝不能少,否则脚本将一直处于inotifywait命令阶段,不会进入到下一步的循环阶段。但需要注意,脚本中(子shell)的后台进程在脚本结束的时候不会随之停止,而是挂靠在pid=1的init/systemd进程下,这种情况下可以直接使用 killall script_file 的方式来停止脚本,这样脚本中的后台也会中断。

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年12月25日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基础知识
  • 文件动态同步
    • 示例脚本
    • inotify 不足之处
    • inotify 的 bug
      • inotify+rsync的缺陷
      • inotify+rsync 的最佳实现
      • 参考资料
      相关产品与服务
      云服务器
      云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档