01 关于inotify
首先在我们实操之前,让我们先来了解下什么是 inotify。
在 Linux 内核 2.6.13 (June 18, 2005)版本之后,Linux 内核新增了一批文件系统的扩展接口(API),其中之一就是inotify,inotify 提供了一种基于 inode 的监控文件系统事件的机制,可以监控文件系统的变化如文件修改、新增、删除等,并可以将相应的事件通知给应用程序。
inotify 既可以监控文件,也可以监控目录。当监控目录时,它可以同时监控目录本身以及目录中的各文件的变化。此外,inotify 使用 文件描述符 作为接口,因而可以使用通常的文件I/O操作select、 poll 和 epoll 来监视文件系统的变化。
总之,简单来说就是:inotify 为我们从系统层面提供了一种可以监控文件变化的接口,我们可以利用它来监控文件或目录的变化。
inotify常用监控事件
inotify 提供常用的监控事件如下:
IN_ACCESS
文件被访问时触发事件,例如一个文件正在被read时。
IN_ATTRIB
文件属性(Metadata)发送变化触发的事件,例如文件权限发生变化(使用 chmod 修改),文件所属用户发生变化(使用chown修改),文件时间戳发生变化等。
IN_CLOSE_WRITE
当一个文件写入操作结束文件被关闭时触发。
IN_CLOSE_NOWRITE
当一个文件或目录被打开没有任何写操作,当被关闭时触发。
IN_CREATE
当一个文件或目录被创建时触发。
IN_DELETE
当一个文件或目录被创建时触发。
IN_DELETE_SELF
监控文件或目录本身被删除时触发,而且,如果一个文件或目录被移到其它地方,比如使用mv 命令,也会触发该事件,因为 mv 命令本质上是拷贝一份当前文件,然后删除当前文件的操作。
IN_MODIFY
文件被修改时触发,例如:有写操作( write) 或者文件内容被清空(truncate)操作。不过需要注意的是,IN_MODIFY 可能会连续触发多次。
IN_MOVE_SELF
所监控的文件或目录本身发生移动时触发。
IN_MOVED_FROM
文件或目录移除所监控目录。
IN_MOVED_TO
文件或目录移入所监控目录。
IN_ALL_EVENTS
监控所有事件。
IN_OPEN
文件被打开事件。
IN_CLOSE
文件被关闭事件,包括 IN_CLOSE_WRITE 和 IN_CLOSE_NOWRITE 的事件总和。
IN_MOVE
涉及所有的移动事件,包括 IN_MOVED_FROM 和 IN_MOVED_TO。
如上便是inotify提供给我们的常用监听事件,我们可以在自己的项目中监听如上的一个或多个事件来实现特定的需求,如果想查阅更多事件细节,请参考:
但需要说明的是,inotify 并非是跨平台的,所以在 macOS 或 windows 下则无法使用,但在macOS也提供类似的实现:FSEvents()以及windows下的 FindFirstChangeNotificationA()
这里我们不再展开跨平台实现讨论,读者要是有兴趣可以查阅相关资料,或者使用文末推荐的开源库。
02 代码实现
接下来,我们开始实现项目的配置文件更新监控功能(实操)。在GO语言中,我们使用golang.org/x/sys/ unix ()这个包来调用底层操作系统的一些封装功能,inotify相关接口也包含在此包中,使用时只需要导入此包即可:
最简单的使用inotify大致分为三个步骤:
- inotify初始化;
- 添加文件监听,设置需要监听的一个事件或多个事件;
- 获取监听到的事件。
我们将按照这三个步骤来实现一个简单的 GO版 配置文件监控脚本 demo ,此处我们还是继续沿用上一篇文章 的配置文件,当该文件发生变化时,我们需要通知Go代码重新读取该文件内容,从而实现热更新的目的 。
2.1 初始化inotify
按照之前所说的步骤,第一步需要初始化inotify,初始化需要使用:InotifyInit() 函数,该函数会返回一个 文件句柄 和错误信息,之后的操作都是基于该文件句柄:
2.2 添加文件监听
完成inotify初始化后,接着我们需要添加我们需要监控的文件和以及想要监听的一个或多个事件,由于是项目的配置,此处的使用场景是:配置文件一般不会有删除的需求,而通常的操作是部署更新,因此此处我们选择的监听事件是:
如第一小节中提到的,当所监听的配置文件以写的方式被打开后,当此文件关闭时触发的事件,在这种情况下就可能发生文件的更新,正好此种场景正是我们想要的。
如上代码中,文件监听使用了 InotifyAddWatch() 函数,第一个参数 fd 为第一步中的初始化文件句柄,第二个参数:path 为需要监听文件的路径,第三个参数为需要监听的事件。如果监听失败,较友好的方式是我们需要关闭当前的文件句柄。
2.3 获取监听事件
有了前两个步骤的准备,那么接下来我们只需要读取获取到监听事件即可:
在这里,我们新起了一个goroutine, 因为接收事件通知是一个循环往复的过程。然后我们把文件句柄中的事件使用 unix.Read() 函数读取到一个 buffer 中,如果unix.Read() 读取不到任何事件,那么它就会处于阻塞状态。然后,我们循环遍历的方式,获取到 buffer 中的所接受到的所有事件通知,然后上报到 events 通道中。
那么现在一旦有新的监控事件通知,那么就会立即达到 events 通道中,接着我们需要做的便是从events 通道中获取通知事件即可:
最终,整个代码大致如下:
如上的代码,我们其实就完成了一个简单的配置文件监控的代码思路,但整体代码的质量是纯面条式的,因此有必要封装一下,在这里我打算把它们封装成一个 Watcher() 类(其实Go语言没有类的概念,实质就是一个 struct ),代码内容请参考链接地址,这里不再此处展开,因为编程思想又是另一个话题,有了这个struct之后,我们只需要直接使用即可:
03 总结
至此,我们完成了一个基于inofity的配置文件热更新全部代码,在Go中来实现还算比较简单,接下来我们需要总结一下:
- inotify 是 Linux 是内核系统提供的监控系统,使用它做热更新,其实和语言无关,所以你可以熟悉的语言来开发。
- inotify需要内核版本为2.6.13以上,不支持macOS 和 Windows系统,如果希望实现跨平台文件监控那么可以使用如下第三点的 fsnotify 库。
- 如果不想重复早轮子,那么我们可以站在巨人的肩上,推荐两个文件监听库:
- (360大拿阿钢出品)
- (跨平台文件监控)
(本文是360技术原创文章,转载请注明出处)
关于360技术
360技术是360技术团队打造的技术分享公众号,每天推送技术干货内容
更多技术信息欢迎关注“360技术”微信公众号