挖掘JDK监控文件中的一个错误

106 阅读4分钟

[

Dwen

](medium.com/@ddwen?sour…)

德文

关注

6月3日

-

4分钟阅读

[

拯救

](medium.com/m/signin?ac…)

挖掘JDK监控文件中的错误

处理意想不到的bug

照片:Kevin BhagatonUnsplash

在一些业务场景中,我们需要自己实现监控文件内容变化的功能,比如监控一个文件是否有变化,当文件内容发生变化时要重新加载。

这似乎是一个比较简单的功能,但在某些JDK版本下,可能会出现意想不到的bug。

本文将带大家看一个相应功能的简单实现,并分析相应的BUG和优缺点。

为了监控文件变化和读取文件,简单的思路如下。

  • 启动一个单线程,定期获取文件最后更新的时间戳(单位:毫秒)
  • 比较最后的时间戳,如果不一致,说明文件已经被修改,然后重新加载

下面是一个简单函数实现的演示(不包括定时任务部分)。

在上面的代码中,首先创建一个文件(方便测试),然后两次读取文件的修改时间,并用LAST_TIME 记录最后的修改时间。

如果文件的最新修改时间与上次不一致,则更新修改时间并进行业务处理。

示例代码中的两次for循环是为了演示改变和没有改变这两种情况。

执行该程序并打印日志如下。

File has been updated: xxxxxxxfile not updated

执行结果与预期一致。

这个方案显然有两个缺点。

不可能实时察觉到文件的变化。毕竟,在程序轮换中存在时间差。

lastModified返回的时间单位是毫秒。如果两个变化发生在同一毫秒内,而预定的任务查询恰好在这两个变化之间,那么后一个变化就无法被感知。

第一个缺点对业务的影响不大。第二个缺点的概率相对较小,可以忽略不计。

上述代码实现后,在正常情况下没有问题,但如果你使用的Java版本是8或9,可能会出现意想不到的bug,这是由JDK本身的bug引起。

编号为JDK-8177809的错误描述如下。

BUG的地址是。 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8177809

这个bug的基本描述是:在某些版本的Java 8和9中,lastModified方法返回的时间戳不是毫秒,而是秒,这意味着返回结果的最后三位数总是0。

让我们写一个程序来验证一下。

在上面的代码中,首先,创建一个文件,然后在for循环中不断向该文件写入内容,并读取修改时间。

每个操作休眠100ms,这样就可以在同一秒内多次写入文件和读取修改时间。

执行结果如下。

File modification time: xxxxxx100File modification time: xxxxxx100File modification time: xxxxxx100File modification time: xxxxxx100File modification time: xxxxxx100File modification time: xxxxxx100File modification time: xxxxxx200File modification time: xxxxxx200File modification time: xxxxxx200File modification time: xxxxxx200

修改了10次文件的内容,只感受到两次。

这个JDK bug使得这个执行的第二个缺点被无限放大。在同一秒内发生变化的概率远远大于在同一毫秒内发生变化的概率。

PS:在官方的Bug描述中,提到可以通过Files.getLastModifiedTime获得时间戳,但作者验证的结果还是无效的。也许不同的版本有不同的表现。

Java 8是目前的主流版本,由于JDK的这个bug,不可能改变JDK。因此,我们需要用其他方式来实现这个业务功能,也就是增加一个文件(或其他存储方式)来记录文件的版本(version)。

这个版本的值可以通过在写文件时递增版本号来产生,也可以通过对文件内容进行MD5计算得到。

如果版本顺序可以保证,使用时只需读取版本文件中的值进行比较。如果它有变化,就重新加载,如果没有变化,就不处理。

如果使用MD5形式,需要考虑MD5算法的性能和MD5结果的碰撞问题(概率很小,可以忽略)。

下面是版本形式的演示。

执行上述代码,打印日志如下。

The version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performedThe version has changed, and business processing is performed

正如你所看到的,每个文件的变化都可以被感知。当然,上面的代码只是一个例子,在使用过程中还需要改进更多的逻辑。

这篇文章实现了一个很常见的功能。起初,它采用的解决方案与传统观念非常一致。结果,它恰好遇到了JDK中的一个bug,不得不改变策略来实现它。

当然,如果商业环境中已经存在一些基本的中间件,那么就会有更多的解决方案。

感谢你阅读这篇文章。