之前遇到个文件监听变更的问题,刚好这周末有空研究了一番,整理出来分享给大家。
从一次故障说起
我们还是从故障说起,这样更加贴近实际,也能让大家更快速理解背景。
有一个下发配置的服务,这个配置服务的实现有点特殊,服务端下发配置到各个服务的本地文件,当然中间经过了一个agent,如果没有agent也就无法写本地文件,然后由client端的程序监听这个配置文件,一旦文件有变更,就重新加载配置,画个架构图大概是这样:
今天的重点是文件的变更该如何监听(watch),我们当时的实现非常简单:
- 单独起个线程,定时去获取文件的最后更新时间戳(毫秒级)
- 记录每个文件的最后更新时间戳,根据这个时间戳是否变化来判断文件是否有变更
从上述简单的描述,我们能看出这样做有一些缺点:
- 无法实时感知文件的变更,感知误差在于查询文件最后更新时间的间隔
- 精确到毫秒级,如果同一毫秒内发生2次变更,且轮询时刚好落在这2次变更的中间时,后一次变更将无法感知,但这概率很小
还好,上述两个缺点几乎没有什么大的影响。
但后来还是发生了一次比较严重的线上故障,这是为什么呢?因为一个JDK的BUG,这里直接贴出罪魁祸首:
BUG详见:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8177809
在某些JDK版本下,获取文件的最后更新时间戳会丢失毫秒精度,总是返回整秒的时间戳,为了直观感受,写了个demo分别在 jdk1.8.0_261 和 jdk_11.0.6 测试(均为MacOs):
- jdk_1.8.0_261
- jdk_11.0.6
如果是在这个BUG的影响下,只要同一秒内有2次变更,且读取文件最后时间戳位于这2次变更之间的时间,第2次变更就无法被程序感知了,同1秒这个概率比同一毫秒大的多的多,所以当然就被触发了,导致了一次线上故障。
这就好比之前是沧海一粟,现在变成了大海里摸到某条鱼的概率。这也能被我们碰到,真是有点极限~
WatchService—JDK内置的文件变更监听
当了解到之前的实现存在BUG后,我就去搜了一下Java下如何监听文件变更,果然被我找到了 WatchService 。