使用 inotify 踩过的坑

Inotify 用于监听文件系统的变更,我们有个场景需要监听一个根目录下的所有文件,包括不同深度子目录下的文件,如果文件发生变化,增量读取新增的内容。

之前我们使用轮询的模式,周期性递归遍历根目录,对比文件属性信息选择要读取的文件,这种方式有一个问题就是周期很难选择:

  1. 如果选择较小的周期,读文件的磁盘 I/O 会相对均匀,但是频繁的递归遍历会造成较高的 CPU 占用
  2. 如果选择较大的周期,递归遍历占用的资源将会下降,但是周期变大造成增量数据变大,结果每次读取将会造成瞬间较高的磁盘 I/O

所以我们考虑用系统级别的 inotify 替换掉轮询的逻辑,结果踩了不少坑。。。

1. JDK 封装的问题 —— poller

jdk 将 inotify 的功能封装为 WatchService,使用起来非常简单,sample 中还提供了一个递归注册目录的例子 WatchDir.java

由于 inotify 只支持 1.监听文件,2.监听目录,以及目录下的目录和文件,不能监听子目录下的变更,所以需要递归注册目录。

基于官方的 sample 我们很快就用上了 inotify,效果很不错,不仅 CPU 占用下降了,而且文件读取特别均匀。

直到有一天 CPU 使用率开始飚高了,通过 top 和 jstack 发现占用 CPU 最多的线程不是读文件的线程,而是 LinuxWatchService 中的 poller 线程。

poller 线程的作用如上图所示:

  1. poll requestList,处理业务代码中的目录注册、取消和关闭等请求;
  2. poll inotify 文件描述符获取 inoitfy_event, 然后按照 WatchKey (也就是 watch descriptor)放到不同的队列,队列的长度默认为 512,如果队列满了,那么清空队列并且放入 overflow 事件。

由于我们监听的目录比较多,当大量文件频繁修改的时候是会产生大量的事件,而 poller 线程会尽最大能力读取这些事件,所以造成了很高的 CPU 占用。

然后这是一种无用功,因为 1.事件数量超出 WatchEvent 队列长度,上层业务逻辑也就取不到事件;2. inotify 的文件队列也有简单的事件去重逻辑,没必要特别快地读取事件然后再去重。

实际上 poller 的速度应该受制于下游的处理能力,如果下游已经不能处理,那么暂时放在中转队列也是意义不大的,我们只需要依赖 inotify 的文件队列即可。

顺便提一下 inotify 相关的几个系统参数:

  1. /proc/sys/fs/inotify/max_user_instances 初始化 ifd 的数量限制
  2. /proc/sys/fs/inotify/max_queued_events ifd 文件队列长度限制
  3. /proc/sys/fs/inotify/max_user_watches 注册监听目录的数量限制
  4. 既然是文件描述符,当然也受 /etc/security/limits.conf 和 /proc/sys/fs/file-max 限制

所以我们通过 JNI 调用重新实现了 WatchService 的逻辑,去掉了 poller 线程,由业务线程控制 poll 的频率。

2. 莫名其妙的 MODIFY 事件 —— deleted

然后一切又变正常了,只是偶尔有几条莫名其妙的 MODIFY 事件。

读到 MODIFY 事件,首先需要读取文件属性,然后走不同的逻辑,但是此刻却抛出 NoSuchFileException。

这个问题一开始也毫无头绪,甚至怀疑是不是文件瞬间被删除了。。。

直到有一天想起了 lsof 这个命令,验证了一下:

REG                8,6        16    6161077 /xxx/xxx/xxx/xxx (deleted)

果然是文件被删除了,但是打开的文件描述符没有关闭,而且还偶尔在写数据。

3. 有目录脱离监听 —— overflow

然后一切又变正常了,文件不存在的 MODIFY 事件归咎于业务方程序的问题。

但是又遇到了新的问题,偶尔发现存在没有读取的文件,而且是整个子目录下的所有文件都没有读。

由于我们监听的目录比较多,有些机器上有约五千个监听目录,所以偶尔会发生事件 overflow,我们猜测是目录创建的事件被 overflow 了,所以没有监听到这个目录。

针对这个问题,我们添加了一个周期性重新递归注册的机制。

由于 inotify 是基于 inode 的,所以重复注册目录获得的 wd (watch descriptor) 是一样的,但是如果目录被移除然后重建了,那么就是一个新的 wd。

此处由于一个 bug 使得问题更严重,由于需要构建文件的绝对路径,我们采用 guava 的 BiMap 存储 wd 与 path 之间的映射,完成 inotify_add_watch 之后需要更新 wdToPath

调用 BiMap 的 put 方法,如果存在遗留的 path,那么是会抛出 IllegalArgumentException 的,所以首先需要处理这种情况,然后调用 forcePut 覆盖旧的映射。

4. 隐藏最深的 mv

移动监听目录这个场景,由于写代码的时候没有考虑,第一次发现这个问题着实被吓了一下。

有些业务方的重启脚本有保存历史日志的逻辑,也就是 mv 旧的日志根目录,然后创建一个新的日志根目录。

由于 inotify 是基于 inode 的,所以 mv 后的目录还在监听中,并且 wd 没有变化,所以目录下文件的改动还是会触发事件。触发事件不可怕,关键是通过 wdToPath 映射还原的绝对路径已经不对了。

在我们的使用场景中,将 mv 作为删除处理即可,所以注册目录时默认加上 IN_MOVE_SELF 事件监听,如果收到该事件,那么 forceUnregister 这个 wd,如果事件 overflow 了,那么还是依赖周期性重新注册的机制。

参考

本文只是粗略记一下遇过的问题,inotify 的详细介绍可以参考下述文章。

  1. Filesystem notification series by Michael Kerrisk
  2. Monitor Linux file system events with inotify


如果觉得我的文章对您有用,请随意打赏。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,400评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,107评论 1 32
  • 高度临在阅读 121评论 0 0
  • 100天可以养成一种习惯,100天可以有惊人的蜕变,100天的时间,100天的陪伴,100天的成长...........
    娇之语阅读 205评论 0 0
  • 文/静仁 我突然想摊开一张红白相间的信纸 写一封信给你 像小时候母亲要我写信给她在外打工的其他儿女 带上橡皮和字典...
    赵静仁阅读 419评论 0 0