原文: http://onlychoice.github.io/blog/2013/10/03/tailing-log-files-in-java/
问题
应用中有时候会有读取日志文件,并做近实时分析的需求(日志监控等)。但是使用类似Log4j的日志框架,日志文件可能会滚动:老的日志文件重命名成其它文件名(比如以日期为后缀),生成一个与老文件同名的新文件,这时候就需要读取日志文件的线程能够正确区分新老文件,并读取相应更新并且不会漏读数据。当然,这个问题的前提是:日志文件本身只会append,而不会在文件中间写入或者删除。本文主要分享下解决这个问题时碰到的一些问题及解决方案。
问题分析
首先我们来看看日志更新这件事有哪些情况可能会出现:
- 文件没有更新;
- 文件有更新,并且没有新的文件生成;
- 文件没有更新,并且有新的文件生成;
- 文件有更新,并且有新的文件生成;
也就是我们要解决两个问题:1)检测文件是否有更新,如果有更新,则读取更新;2)检测文件是否有滚动,也就是是否有新文件生成。
检测更新
检测更新有两种可能的方法:
- 通过文件更新时间:File.lastModified可以获取;
- 通过文件大小:File.length或者 FileChannel.size。
文件更新时间
通过文件更新时间检测更新应该是最直观的方法,但是这个方法有一个小缺陷:更新时间的精度。根据Wiki上的介绍,ext3的更新时间精度是一秒,ext4本身可以返回纳秒级别的更新时间,但是在这两种文件系统中,Java返回的都是秒级别的更新数据。这会导致什么问题呢?如果在1秒内有多次更新,那么有可能无法准确的检测到更新,如:
lastmodified: 1365590117000, size: 10597
lastmodified: 1365590117000, size: 10610
如上所示,是测试中的一个样例,文件大小已变,但是更新时间并没有变化,导致无法检测到更新。当然本身这个问题影响并不大,如果文件后续又有更新,前面的更新还是会读到的。
文件大小
用文件大小来判断一个文件是否有更新应该是最准确:只要文件哪怕更新一个字节,也会立即检测到文件有更新。
- File.length拿到的永远是当前路径对应文件的大小,如果日志文件滚动,那么它拿到的就是新的日志文件的大小;
- FileChannel.size则永远拿到当前channel对应物理文件的大小,即使文件滚动,老的文件被重命名,这个size拿到的还是老的文件大小。
文件滚动
在Linux中,inode是一个文件的唯一标识,不管文件是否重命名过。但是在Java中,却不存在这样一个接口来获取inode。在这种情况下,我们怎么来判断是否有文件生成?
- 通过文件大小及更新时间(忽略精度问题):上面提到FileChannel.size可以拿到老文件的大小,如果在两次检测之间,这个大小并没有变化,并且文件更新时间有变化(通过File.lastModified获取到,拿到的可能是新文件的更新时间),则可以肯定有新文件生成;
- 通过Runtime.exec(或者ProcessBuilder),来调用shell命令“ls -i”来获取文件的inode;
- 通过JNI来获取文件的inode。
如果不用获取inode,就可以判断新文件是否生成,那应该是最好的了,可惜并不完美:上述第一种方案的问题是,如果老的文件有更新,并且有新的文件生成,则检测不到已经有新文件生成了。在这种情况下,如果新的文件在滚动之前又有更新,则不会丢失数据(跟lastModified的精度问题类似),否则有可能会丢失数据。在实际使用中,如果检测间隔在毫秒级,这种情况应该很少出现。
那通过Runtime.exec获取inode可以吗,毕竟不用写jni代码。但是实际的情况是:1)每次获取inode花费的时间平均在10毫秒以下,如果检测间隔在100毫秒,还可以接受;2)通过这种方式获取inode不稳定,测试中发现有时候获取一次inode的时间会接近1s;3)Runtime.exec是通过fork一个进程,在新进程中执行shell命令的,万一系统中已经无法创建进程,那就会阻塞我们的检测线程;4)测了一下性能,分别通过Runtime.exec及JNI获取1000次inode,JNI耗时2ms,Runtime.exec耗时8s多,JNI的方式比Runtime.exec快了4000倍。。。
实现
上面把问题基本已经分析清楚,那处理逻辑就比较简单下:
- 如果当前文件的inode与上一轮文件的inode不同,则认为有新的文件生成:如果FileChannel.size比上一轮的大,则先读取老文件更新;读取新文件的内容;
- 如果当前文件的inode与上一轮文件的inode相同,则没有新文件生成,只需要通过FileChannel.size来判断老文件是否有更新,如果有更新则读取。
NativeLoader
做为一个工具类,如果在使用的时候还要配置一些参数什么的,那么无疑会比较麻烦。最简单的用法应该就是把打包好的jar发布到maven,使用方只要写好依赖,直接使用就行。为此,在打包时,就预先将编译好的动态库打包进去,并且由lib本身来加载动态库。NativeLoader就是完成从jar中加载动态库的功能。
总结
最终代码见:log-tailer,只支持Linux及MacOS。
相关推荐
在Java编程语言中,文件读取是常见的任务,可以用于处理各种类型的数据,如文本、图像、音频等。本文将详细介绍Java中四种不同的文件读取方法:按字节读取、按字符读取、按行读取以及随机读取。 1. **按字节读取...
在Java编程语言中,读取文件是一项常见的操作,尤其是在处理数据、日志文件或配置信息时。本文将详细解析如何使用Java读取文本文件,基于提供的代码示例,深入探讨其工作原理及最佳实践。 ### Java读取文本文件的...
总的来说,这个“java日志文件过滤”项目提供了一个便捷的工具,帮助开发者快速处理和分析日志数据,提高了问题排查和系统监控的效率。通过学习和理解这个项目,你可以深化对Java日志处理、文件I/O和条件过滤等技术...
为了提高效率,可以考虑使用`java.nio`包中的缓冲和通道API来提高文件读取速度。 以下是一个简化的代码示例,展示了如何开始读取STL文件: ```java import java.io.BufferedReader; import java.io.FileReader; ...
在Java编程中,读取TXT文件并将其内容存入数据库是一项常见的任务,特别是在数据处理、日志分析或者导入批量数据的场景下。以下是一个详细的知识点解析,涵盖了如何使用Java来实现这一操作。 1. **读取TXT文件** -...
在Java编程中,读取数据库表中的内容并将其转换为SQL文件,然后解析并执行这个文件,是一项常见的数据处理任务。这项操作可能涉及到数据库连接、数据查询、文件操作以及SQL语句的构建与执行。以下将详细介绍这个过程...
此外,描述中还提到了“java读取压缩文件解包压缩文件等各种文件操作类”,这意味着这个项目可能包含了一套完整的文件操作工具集,包括: - **压缩文件**:使用`ZipOutputStream`可以创建ZIP文件,将多个文件或目录...
在这个改进的版本中,我们创建了一个`Logger`实例,设置了日志文件和日志级别(这里是`INFO`)。当读取属性文件或进行其他操作时,如果发生异常,我们会使用`log()`方法记录错误信息。这样,我们可以在`app.log`文件...
在Java编程语言中,文件读取是常见的操作,无论是在处理配置文件、日志记录,还是进行数据交换,都离不开对文件的操作。本教程将详细讲解如何在Java中实现文件读取,以“JAVA 文件读取示例”为主题,通过实际代码...
在Java编程语言中,读取文本文件是一项基本且常见的任务,尤其在处理数据输入、日志分析或配置文件操作时。下面将详细讲解如何使用Java读取文本文件,包括多种常用的方法和注意事项。 1. 使用`BufferedReader` `...
- **逐行读取**:使用BufferedReader的`readLine()`方法可以方便地逐行读取文本文件,这是处理日志文件或文本数据的常见方式。 - **随机访问**:如果需要在文件中定位特定位置,可以使用RandomAccessFile类,它...
在Java编程中,将数据从TXT文件读取并导入MySQL数据库是一项常见的任务,涉及到文件操作、字符串处理以及数据库交互等多个知识点。以下是对这些关键概念的详细解释: 1. **文件操作**:`FileOper.java`可能包含了对...
在Java中处理GRIB2文件通常需要借助特定的库,例如ECMWF(欧洲中期天气预报中心)的JGRIB2库或Unidata的NetCDF-Java库。下面将详细介绍如何使用Java进行GRIB2文件的读取和解析。 1. **理解GRIB2文件结构**: GRIB2...
Java 读取配置文件是开发过程中常见的需求,用于获取应用程序运行时所需的参数或设置。配置文件通常包含关键的系统信息,如数据库连接字符串、API密钥等,它们允许程序根据不同的环境或用户需求进行动态配置。Java ...
此段代码展示了基本的文件读取过程,但是原文件中的部分代码似乎尝试进行更复杂的数据处理,包括数据解析、计算和排序。这部分代码涉及到了数据的结构化存储(如使用`List`和`Map`),以及数学运算和排序算法。 ###...
在Java编程环境中,读取DLT645协议文件通常涉及到智能电表通讯,这是一个用于电力系统中远程抄表的标准。DLT645是一种基于串行通信的协议,允许设备(如电表)与主站系统进行数据交换。为了在Java中实现对这种文件的...
### Java:二进制方式读取文件 #### 知识点概述 在Java中,处理文件的二进制读写是非常常见的操作。本篇文章将基于提供的代码示例,深入探讨如何使用`FileInputStream`和`FileOutputStream`进行二进制文件的读取和...
1. **使用FileInputStream和BufferedReader**:创建一个`FileInputStream`对象来打开日志文件,然后用`BufferedReader`进行读取。通过循环读取每一行,一旦有新的日志被写入,就可以立即显示。这种方法简单但可能不...
在Java编程中,读取文件、对数据进行排序并重新写入文件是常见的操作,尤其在数据处理和分析场景中。下面将详细讲解这个过程,包括相关知识点和具体实现。 首先,我们需要导入Java的`java.io`包,该包包含了处理...
标题 "java 读取Ftp指定位置的文件解析并入库" 涉及到的是使用Java编程语言通过FTP(File Transfer Protocol)协议从远程服务器上下载文件,然后对下载的文件进行解析,并将解析得到的数据存储到数据库中。...