本文介绍下用法、注意事项、pyinotify多进程实现数据同步
Inotify的出身:
Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么从而能够更好地管理设备,给用户提供更好的服务。inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知,该机制是著名的桌面搜索引擎项目 beagle 引入的,并在 Gamin 等项目中被应用。
Inotify优点:
之前的一种机制:dnotify有很多缺陷,被监视的目录都会导致过多的文件描述符,对于移动存储设备无法umount;监控对象基于目录,对于文件的变化需要缓存更多的stat结构数据。实现接口使用signal不是很友好;
1、Inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
2、Inotify 既可以监视文件,也可以监视目录
3、Inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
4、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)
更多原理部分请参考: http://www.ibm.com/developerworks/cn/linux/l-inotifynew/
官网: https://github.com/rvoicilas/inotify-tools/wiki
查看内核是否支持inotify机制
grep INOTIFY_USER /boot/config-$(uname -r)
输出:CONFIG_INOTIFY_USER=y 表示支持inotify机制
grep INOTIFY_USER /boot/config-$(uname -r)
输出:CONFIG_INOTIFY_USER=y 表示支持inotify机制
安装部分:
yum install inotify-tools (版本为3.13)
inotify-tools包含两个工具inotifywait(监测事件的发生);inotifywatch(事件变化统计)
使用方法:
可以通过man 解决:man inotifywait;man inotifywatch;man inotify
相关参数设置:
/proc/sys/fs/inotify/max_queued_events 被监测对象的队列最大数(对于较多文件的情况,适当增大)
/proc/sys/fs/inotify/max_user_instances 被监测对象最大数,默认为8192
官方简单脚本举例:
#!/bin/sh # get the current path CURPATH=`pwd` inotifywait -mr --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' \ -e close_write /tmp/test | while read date time dir file; do FILECHANGE=${dir}${file} # convert absolute path to relative FILECHANGEREL=`echo "$FILECHANGE" | sed 's_'$CURPATH'/__'` rsync --progress --relative -vrae 'ssh -p 22' $FILECHANGEREL [email protected]:/backup/root/dir && \ echo "At ${time} on ${date}, file $FILECHANGE was backed up via rsync" done
该脚本简单的精妙,但也存在不少不足;
1、脚本执行为单进程,对于含有多个文件的情况需要考虑并发执行
2、wait会产生很多冗余事件;比如对于在文件中写数据,打开文件都会产生临时文件a`或者a.swp a.swpx 文件,让rsync产生更多的冗余计算;
具体测试文章参考: http://hi.baidu.com/johntech/item/282552cfe6edb735449416e3
3、错误处理机制,脚本出现错误的处理的问题,比如rsync 连接失败,是否隔一段时间重连等?
一款开源工具sersync http://code.google.com/p/sersync/
但为了方便,个人使用了pyinotify 模块实现以上功能:
官方项目地址: https://github.com/seb-m/pyinotify/wiki
代码示例为:
稍后填充!
#!/usr/bin/env python #encoding=utf8 import os import subprocess import time import sys from pyinotify import WatchManager, Notifier,ProcessEvent,IN_DELETE, IN_CREATE,IN_MODIFY class rsync_file_cmd(): def __init__(self,src_file,dst,dst_file): self.src_file=src_file self.dst=dst self.dst_file=dst_file self.cmd='rsync -arz --timeout=60 -e "ssh -p 22" %s %s:%s' %(self.src_file,self.dst,self.dst_file) self.del_cmd='ssh -p 22 %s "rm -rf %s"' % (self.dst,self.dst_file) class EventHandler(ProcessEvent): """Handle""" def process_IN_CREATE(self, event): if event.name.startswith('.') or event.name.endswith('~') or event.name=='4913': pass else: create_sync=rsync_file_cmd(str(event.pathname),'[email protected]',str(event.pathname)) subprocess.call(create_sync.cmd,shell=True) def process_IN_DELETE(self, event): if event.name.startswith('.') or event.name.endswith('~') or event.name=='4913': pass else: delete_sync=rsync_file_cmd(str(event.pathname),'[email protected]',str(event.pathname)) subprocess.call(delete_sync.del_cmd,shell=True) def process_IN_MODIFY(self, event): if event.name.startswith('.') or event.name.endswith('~') or event.name=='4913': pass else: modify_sync=rsync_file_cmd(str(event.pathname),'[email protected]',str(event.pathname)) subprocess.call(modify_sync.cmd,shell=True) def FSMonitor(path='/root/wpf'): wm = WatchManager() mask = IN_DELETE | IN_MODIFY | IN_CREATE notifier = Notifier(wm, EventHandler(),read_freq=10) notifier.coalesce_events() # 设置受监视的事件,这里只监视文件创建事件,(rec=True, auto_add=True)为递归处理 wm.add_watch(path,mask,rec=True, auto_add=True) notifier.loop() if __name__=='__main__': try: pid = os.fork() if pid > 0: sys.exit(0) except OSError, e: print >>sys.stderr, 'fork failed: %d (%s)' % (e.errno, e.strerror) sys.exit(1) os.setsid() os.umask(0) FSMonitor() print 'start!'