当前位置: 首页 > 科技观察

监控日志文件变化的三种方式,推荐第三种!

时间:2023-03-17 15:06:44 科技观察

背??景在研究规则引擎时,如果规则是以文件的形式存储的,需要监控指定的目录或文件,感知规则是否发生变化,然后加载。当然,在其他业务场景中,比如配置文件的动态加载、日志文件的监控、FTP文件变化的监控等,也会遇到类似的场景。本文为大家提供了三种解决方案,并分析了它们的优缺点。建议保存它们以备不时之需。方案一:定时任务+File#lastModified这种方案是最简单最直接可以想到的方案。通过定时任务,轮训查询文件的最后修改时间,并与上次进行比较。如果有变化,说明文件被修改,重新加载或者进行相应的业务逻辑处理。在此处粘贴示例代码:publicclassFileWatchDemo{/***最后更新时间*/publicstaticlongLAST_TIME=0L;publicstaticvoidmain(String[]args)throwsIOException{StringfileName="/Users/zzs/temp/1.txt";//创建一个文件,只是一个例子。实际上,其他程序会触发文件更改createFile(fileName);//执行2次for(inti=0;i<2;i++){longtimestamp=readLastModified(fileName);if(timestamp!=LAST_TIME){System.out.println("文件已更新:"+timestamp);LAST_TIME=时间戳;//重新加载,文件内容}else{System.out.println("文件未更新");}}}publicstaticvoidcreateFile(StringfileName)抛出IOException{Filefile=newFile(fileName);如果(!file.exists()){布尔结果=file.createNewFile();System.out.println("创建文件:"+result);}}publicstaticlongreadLastModified(StringfileName){Filefile=newFile(fileName);返回文件.lastModified();}}对于文件变化频率较低的场景,该方案实现简单,基本可以满足需求。不过在上一篇文章中提到,需要注意Java8和Java9中File#lastModified的bug。但是,如果使用这种方案来改变文件目录,缺点就很明显了。例如,操作频繁,在遍历、保存状态、比较状态时效率低下,无法充分发挥OS的功能。方案二:WatchService在Java7中增加了java.nio.file.WatchService,通过它可以实现对文件变化的监听。WatchService是一个基于操作系统的文件系统监视器。无需遍历、比较,即可监控系统中所有文件的变化。它是一种基于信号发送和接收的高效率监控。publicclassWatchServiceDemo{publicstaticvoidmain(String[]args)throwsIOException{//这里的监听必须是一个目录Pathpath=Paths.get("/Users/zzs/temp/");//创建WatchService,是对操作系统的文件监听器的封装,相比之前,不需要遍历文件目录,效率高很多。WatchService观察者=FileSystems.getDefault().newWatchService();//注册指定目录使用的监听器,监听目录下的文件变化;//PS:Path必须是目录,不能是文件;//StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件path.register(watcher,StandardWatchEventKinds.ENTRY_MODIFY);//创建一个线程来等待目录中的文件发生变化try{while(true){//获取目录中的变化://take()是一个阻塞方法,它在返回之前等待来自监视器的信号。//也可以使用watcher.poll()方法,非阻塞方法,会立即返回此时monitor是否有信号。//返回结果WatchKey是一个单例对象,与前面register方法返回的实例相同;WatchKeykey=watcher.take();//处理文件变化事件://key.pollEvents()用于获取文件变化事件只能获取一次,不能重复获取,类似队列的形式。for(WatchEventevent:key.pollEvents()){//event.kind():事件类型if(event.kind()==StandardWatchEventKinds.OVERFLOW){//事件可能丢失或丢弃continue;}//返回触发事件的文件或目录的路径(相对路径)PathfileName=(Path)event.context();System.out.println("文件更新:"+fileName);}//每次调用WatchService()或poll()方法都需要通过这个方法重置if(!key.reset()){break;}}}catch(Exceptione){e.printStackTrace();}}}上面的demo展示了WatchService的基本用法,注解部分也说明了各个API的具体功能。WatchService监听的文件类型也变得更加丰富了:ENTRY_CREATE目标被创建ENTRY_DELETE目标被删除ENTRY_MODIFY目标被修改OVERFLOW一个特殊的Event,表示该Event被放弃或者丢失如果看WatchService实现类的源码(PollingWatchService),你会发现,本质上,它启动了一个独立的线程来监听文件变化:PollingWatchService(){//TBD:MakethenumberofthreadsconfigurablescheduledExecutor=Executors.newSingleThreadScheduledExecutor(newThreadFactory(){@OverridepublicThreadnewThread(Runnabler){Threadt=newThread(null,r,"FileSystemWatcher",0,false);t.setDaemon(true);returnt;}});也就是说,需要我们手动实现的部分,也由WatchServiceDone帮我们实现了。如果你写一个demo,在验证的时候,你会明显感觉到WatchService监控文件变化不是实时的,有时候监控文件变化需要几秒。以实现类PollingWatchService为例,查看源码,可以看到如下代码:Runnablevar5=newRunnable(){publicvoidrun(){PollingWatchKey.this.poll();}};this.poller=PollingWatchService.this.scheduledExecutor.scheduleAtFixedRate(var5,var2,var2,TimeUnit.SECONDS);}}也就是说,监听器是被一个调度器控制在一个固定的时间间隔,而这个时间间隔是在SensitivityWatchEventModifier类中定义的:publicenumSensitivityWatchEventModifierimplementsModifier{HIGH(2),MEDIUM(10),LOW(30);//...}该类提供了3级时间间隔,分别为2秒、10秒、30秒,默认值为10秒。这个时间间隔可以在path#register中传递:path.register(watcher,newWatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY},SensitivityWatchEventModifier.HIGH);与方案一相比,实现起来简单高效。缺点也很明显。它只能监控当前目录下的文件和目录,不能监控子目录。我们也看到监控只能算准实时,监控时间默认只能取API提供的三个值。该API也被Java7在StackOverflow上提出,在MacOS下存在延迟问题,甚至涉及到Windows和Linux系统。方案三:ApacheCommons-IO方案一自己实现,方案二借助JDKAPI实现,方案三借助开源框架实现,也就是几乎每个人都用的commons-io类库项目介绍。引入相应的依赖:commons-iocommons-io2.7注意不同的版本需要不同的JDK支持,2.7需要Java8及更高版本。commons-io文件监控的实现位于org.apache.commons.io.monitor包下,基本使用流程如下:自定义文件监控类,继承FileAlterationListenerAdaptor处理文件的创建、修改、删除和目录;自定义文件监控类,通过指定目录创建观察者FileAlterationObserver;添加一个文件系统观察器到监视器,并添加一个文件监听器;调用并执行。第1步:创建一个文件侦听器。根据需要在不同的方法中实现相应的业务逻辑处理。公共类FileListener扩展FileAlterationListenerAdaptor{@OverridepublicvoidonStart(FileAlterationObserverobserver){super.onStart(observer);System.out.println("开始");}@OverridepublicvoidonDirectoryCreate(Filedirectory){System.out.println("New:"+directory.getAbsolutePath());}@OverridepublicvoidonDirectoryChange(Filedirectory){System.out.println("Modify:"+directory.getAbsolutePath());}@OverridepublicvoidonDirectoryDe??lete(Filedirectory){System.out.println("删除:"+directory.getAbsolutePath());}@OverridepublicvoidonFileCreate(Filefile){StringcompressedPath=file.getAbsolutePath();System.out.println("新建:"+compressedPath);if(file.canRead()){//TODO读取或重新加载文件内容System.out.println("Filechanged,process");}}@OverridepublicvoidonFileChange(Filefile){StringcompressedPath=file.getAbsolutePath();System.out.println("修改:"+compressedPath);}@OverridepublicvoidonFileDelete(文件文件){System.out.println("删除:"+file.getAbsolutePath());}@OverridepublicvoidonStop(FileAlterationObserverobserver){super.onStop(observer);System.out.println("停止");}}第二步:封装一个文件监控的工具类。核心是创建一个观察者FileAlterationObserver,封装文件路径Path和监听器FileAlterationListener,然后交给FileAlterationMonitor公共类FileMonitor{私有FileAlterationMonitor监视器;公共FileMonitor(长间隔){monitor=newFileAlterationMonitor(间隔);}/***添加监听器到文件**@parampath文件路径*@paramlistener文件监听器*/publicvoidmonitor(Stringpath,FileAlterationListenerlistener){FileAlterationObserverobserver=newFileAlterationObserver(newFile(path));monitor.addObserver(观察者);observer.addListener(侦听器);}publicvoidstop()throwsException{monitor.stop();}publicvoidstart()throwsException{monitor.start();}}第三步:调用并执行:publicclassFileRunner{publicstaticvoidmain(String[]args)throwsException{FileMonitorfileMonitor=newFileMonitor(1000);fileMonitor.monitor("/Users/zzs/temp/",newFileListener());文件监控器.start();}}执行程序,你会发现每1秒就进入一次日志。当文件发生变化时,也会打印相应的日志:onStartmodification:/Users/zzs/temp/1.txtStoponStartonStop当然可以在创建FileMonitor时修改相应的监控间隔。在这个方案中,监听器本身会启动一个线程进行定时处理。每次运行时,会先调用事件监听处理类的onStart方法,然后检查是否有变化,调用对应事件的方法;例如onChange文件内容发生变化,检查后调用onStop方法释放当前线程占用的CPU资源,等待下一个interval被唤醒再次运行。监听器是以文件目录为根,也可以设置过滤器监听相应的文件变化。Filter的设置可以在FileAlterationObserver的构造方法中找到:publicFileAlterationObserver(StringdirectoryName,FileFilterfileFilter,IOCasecaseSensitivity){this(newFile(directoryName),fileFilter,caseSensitivity);}总结到此为止,三种基于文件变化的监控方法关于Java程序的介绍。经过上面的分析和例子,你已经看到没有完美的解决方案,你可以根据自己的业务情况和系统的容忍度来选择最合适的方案。