及时的获取更新的配置文件内容

场景

配置文件动态生效的场景本身是特别多的

  • 普罗米修斯的增添抓取实例,他是有filesd的。你只要把新的实例写入到文件中,再下次读取配置文件的时候,就可以生效了。他是通过定时访问配置文件做到的,可以通过配置访问间隔来控制频率。
  • log4j的配置如果想不重启生效,我们一般会使用他默认带的加载方式,这样会定时的去访问path,看看是否是更新了。
	       PropertyConfigurator.configureAndWatch(path,1000);

上面举了几个例子,实现都是通过定时访问的方式实现的。这种实现比较好理解,定时看文件的最后更改时间,如果时间变了,就重新读取一次文件,否则的话则继续等一段时间。

问题

说了这种实现,用过的都有一点问题,就是需要根据具体的场景来设置参数,因为定时的去访问,如果文件没有修改,就是一次无效的执行。代码白跑了一次。例如设置为每2秒跑一次,1小时都不改一次,这都是无效的运行。所以时间应该设置长一点,例如2分钟。但是当修改了以后,最糟糕,得2分钟以后看到效果。这里就出现了资源和生效时间的一个平衡。

java7特性

解决上面平衡问题的一个方式,就是利用java7的目录的事件来替换定时访问的逻辑。
我们先看看支持多少种事件。

    /** * A special event to indicate that events may have been lost or * discarded. * */
    public static final WatchEvent.Kind<Object> OVERFLOW =
        new StdWatchEventKind<Object>("OVERFLOW", Object.class);

    /** * Directory entry created. * */
    public static final WatchEvent.Kind<Path> ENTRY_CREATE =
        new StdWatchEventKind<Path>("ENTRY_CREATE", Path.class);

    /** * Directory entry deleted. * */
    public static final WatchEvent.Kind<Path> ENTRY_DELETE =
        new StdWatchEventKind<Path>("ENTRY_DELETE", Path.class);

    /** * Directory entry modified. * */
    public static final WatchEvent.Kind<Path> ENTRY_MODIFY =
        new StdWatchEventKind<Path>("ENTRY_MODIFY", Path.class);

其实就是增,删,改,丢失。这些事件都是针对目录的,如果你要监控/data/test.txt。那我们就可以给/data注册监控事件,以此看到test.txt的改变。
知道了事件,我们的使用方式就很套路了。

//获取文件系统的WatchService 
WatchService watcher = FileSystems.getDefault().newWatchService();
//给dir(Path)注册一个监控的事件,事件如上
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

	 for (;;) {
            // 等待触发的事件
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }
            for (WatchEvent<?> event: key.pollEvents()) {
                WatchEvent.Kind kind = event.kind();
                //下面是根据事件的业务逻辑
                ……
            }
            // 重置
            boolean valid = key.reset();

        }

上面的代码非常接近nio的socket,都是获取等待事件的通知。

套路化

上面的代码流程非常的套路,我们可以稍微提取一些功能,简化代码。

		final DirWatcher dw = new DirWatcher(true);
		dw.registerAllEvents(Paths.get("c:/hello")).addHandler(new WatcherResultHandler() {
			public void handleResult(Path path, Kind<?> event) {
				...
			}
		}).processEvents();

我们只要关心注册的目录,然后再WatcherResultHandler里写处理事件的逻辑即可。
https://github.com/xpbob/commonio
具体的代码如上,欢迎交流。

注意事项

  • 这个功能是java7里的,所以有jdk的限制
  • 针对文件增的事件。增加的事件是一个比较麻烦的事情,尤其是对网络传输的过程,如果传到一般断了,我们也会受到增加的事件,一般的文件的网络传输都是RandomAccessFile的应用,因为可以把文件分开传递,或者支持从特定位置继续写。这样复杂的逻辑,接收完以后,文件事件处理。需要在事件的处理上做很多的逻辑,例如可以使用中间文件,xx.tmp等等。不处理.tmp的文件,直到合成一个完整的文件为止。加md5文件,当读取到md5文件的时候去检查传输的文件。
点赞