记一次使用tika解析文件文本导致的内存溢出问题
背景
笔者曾供职于某信息安全公司,接到过一个需求,提取文档中的文本以供后续分析。tika是apache开源的解析文档内容的组件,应用十分广泛。tika几乎支持你能想到的所有文档格式,docx , pptx , xlsx , pdf, zip , rar , tar 等。
tika本身只是一个门面,不提供文档解析实现,这有点类似与sl4j。例如tika使用pdfbox解析pdf文件,使用poi解析 office文档。然而文档种类繁多,有压缩的未压缩的,有加密的有未加密的,有大文件有小文件。甚至还有一些恶意文件,例如 压缩炸弹,xml炸弹。这些恶意文件可能会导致整个应用挂掉。
案发
这里先简单介绍一下:使用tika解析文本之后将文本存入ElasticSearch数据库中是一个非常经典的使用场景。一些图书馆,档案馆,将文档内容提取出来存入ES做索引,这样便可以通过内容检索到对应的文档了。
某天,外场报告说ElasticSearch客户端报告了Request cannot be executed; I/O reactor status: STOPPED 错误,导致所有的ES插入报错。
报错堆栈如下:
Caused by: java.lang.IllegalStateException: Request cannot be executed; I/O reactor status: STOPPED
at org.apache.http.util.Asserts.check(Asserts.java:46)
at org.apache.http.impl.nio.client.CloseableHttpAsyncClientBase.ensureRunning(CloseableHttpAsyncClientBase.java:90)
at org.apache.http.impl.nio.client.InternalHttpAsyncClient.execute(InternalHttpAsyncClient.java:123)
at org.elasticsearch.client.RestClient.performRequestAsync(RestClient.java:529)
at org.elasticsearch.client.RestClient.performRequestAsyncNoCatch(RestClient.java:514)
at org.elasticsearch.client.RestClient.performRequest(RestClient.java:226)
at org.elasticsearch.client.RestHighLevelClient.performRequest(RestHighLevelClient.java:1256)
at org.elasticsearch.client.RestHighLevelClient.performRequestAndParseEntity(RestHighLevelClient.java:1231)
at org.elasticsearch.client.RestHighLevelClient.search(RestHighLevelClient.java:730)
看起来是不是挺无厘头的?一般来说越是底层的报错越难排查。在网上可以大量搜到ES这个 I/O reactor status: STOPPED错误,我尝试很多方法,甚至对ES进行了长时间的压力测试,没有复现该问题。
就这样过去了很长一段时间,我在一次偶然的压力测试中发现,日志中赫然出现了OutOfMemoryError错误,并生成了堆转储文件。伴随着这个内存溢出错误,I/O reactor status: STOPPED这个错误也复现了。这强烈提示OutOfMemoryError导致了ES客户端的I/O reactor status: STOPPED错误!后续的多次测试也证明了这一点。
原因
后续就简单了,使用Eclipse MAT工具分析堆转储文件,发现pdfbox相关的对象占用空间特别大。这个pdfbox就是tika解析pdf文件所引入的依赖。
tika使用了pdfbox解析了一个大概40M的pdf文件,直接吃掉了2.1G左右的堆内存!文件的大小和占用的堆内存并没有直接的关联。笔者在之后还遇到了只有40Kb左右但单压缩比极高的docx文件(测试构造的极端文件)。这个40Kb左右的文件直接吃掉了1G以上的堆内存。
接下来就很清晰了,tika解析特定的文件占用大量内存,ES客户端的底层I/O线程申请不到足够的内存,抛出OutOfMemoryError错误,I/O线程意外退出,最终导致了 I/O reactor status: STOPPED
令很多人意外的是,出现内存溢出后,jvm不会退出,如果线程中没有捕获Error(区别于Exception),抛出错误的线程就会退出。如果是关键线程退出(例如定时任务),整个系统就会处于不稳定的状态,带来的问题是难以排查的。我这里的例子就是这样,出现了内存溢出的错误,jvm还在,整个服务仿佛还是正常,但底层的I/O线程挂了,导致所有ES的操作全部失败。当然,如果你的运气比较好,挂掉的线程仅仅是普通的线程(例如线程池中的线程),那么线程池还会拉起一个线程,只是丢失了这个线程执行的任务而已。
解决
tika解析pdf文件可能会占用大量内存,并最终导致内存溢出,在这里我们可以限制pdf文件的内存占用
public String parse() throws Exception { ContentHandler contentHandler = new BodyContentHandler(); //设置pdf文件占用最大内存50M
PDFParserConfig pdfParserConfig = new PDFParserConfig();
pdfParserConfig.setMaxMainMemoryBytes(50 * 1024 * 1024L);
Metadata metadata = new Metadata(); //填入pdf参数
ParseContext parseContext = new ParseContext();
parseContext.set(PDFParserConfig.class, pdfParserConfig); Parser parser = new AutoDetectParser();
try (InputStream in = new FileInputStream(file)) {
parser.parse(in, contentHandler, metadata, parseContext);
return contentHandler.toString();
}
}jvm参数添加 -XX:+CrashOnOutOfMemoryError
出现内存溢出后立刻退出,等待守护进程拉起(需要有守护进程),避免应用处于不稳定状态
除了内存溢出外,tika社区还报告另外两个问题,内存泄漏(memory leak)和 死循环(infinite loop)。上面我们勉强通过参数限制了pdf文件的内存占用,但是文件会有极端文件破坏我们服务的稳定。内存泄漏最总会导致内存溢出,添加虚拟机参数溢出后重启即可解决,可死循环会导致线程永久卡死,直接导致服务不可用。
对于死循环问题,可以通过设定超时时间勉强解决,超时后,进程自动退出,等待守护进程拉起
终极解决方式
上述的解决方式本质上都是通过重启服务的方式实现的。据tika社区介绍,新版本的tika已默认使用fork的方式解析文件。所谓的fork的方式,就是每处理一个文件就用一个单独的进程(当然进程可以复用),这种进程隔离的方式将错误局限在了子进程中而父进程不受影响,实现了隔离和保护。详见:https://dist.apache.org/repos/dist/release/tika/2.1.0/CHANGES-2.1.0.txt
所谓的终极解决方式就是实现进程隔离,将错误限制在子进程中。例如浏览器就是典型的多进程,每个页面都是单独的进程,避免个别页面的崩溃带崩整个应用。实现多进程的核心是进程间的通信,关于这一主题的讨论将放在我的下一篇博客中。
总结
- 使用tika提取文件内容这个行为充满着不确定性,可能会导致内存溢出,内存泄漏,死循环等严重问题
- 不要在你的应用中直接集成tika,应当将内容提取独立成服务
- 内存溢出进程不会退出,只是出现内存溢出错误的线程退出,这让整个服务处于不稳定状态
记一次使用tika解析文件文本导致的内存溢出问题的更多相关文章
- 解决 SqlServer执行脚本,文件过大,内存溢出问题
原文:解决 SqlServer执行脚本,文件过大,内存溢出问题 执行.sql脚本文件,如果文件较大时,执行会出现内存溢出问题,可用命令替代 cmd 中输入 osql -S 127.0.0.1,8433 ...
- 文件上传之--内存溢出(System.OutOfMemoryException)
两周前就想把这点经验记录下来了,由于拖延症上身,直到刚才突然想起这件未完成的任务,今天是1024,在这个特别的日子里,祝所有程序猿兄弟姐妹们节日快乐! 上传功能一直很正常,直到上传了个500多兆的文件 ...
- java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架
产品需求 产品经理需要导出一个页面的所有的信息到 EXCEL 文件. 需求分析 对于 excel 导出,是一个很常见的需求. 最常见的解决方案就是使用 poi 直接同步导出一个 excel 文件. 客 ...
- 小程序里面使用wxParse解析富文本导致页面空白等
在部分安卓手机上会出现白屏的情况且有些ios手机上图文混排上,图片显示不出问题 解决:把插件里面的console.dir去掉即可(原因在于安卓手机无法解析console.dir) 有些图片解析出来下面 ...
- 记一次Orika使用不当导致的内存溢出
hprof 文件分析 2021-08-24,订单中心的一个项目出现了 OOM 异常,使用 MemoryAnalyzer 打开 dump 出来的 hprof 文件,可以看到 91.27% 的内存被一个超 ...
- .net向文件写入字符串流内存溢出的问题
字符串过大导致抛出异常: exceptopm of type 'system.outOfmemoryexception' was thrown 解决方法:逐块写入可以避免这个问题
- 【apache tika】apache tika获取文件内容(与FileUtils的对比)
Tika支持多种功能: 文档类型检测 内容提取 元数据提取 语言检测 重要特点: 统一解析器接口:Tika封装在一个单一的解析器接口的第三方解析器库.由于这个特征,用户逸出从选择合适的解析器库的负担, ...
- apache tika检测文件是否损坏
Apache Tika用于文件类型检测和从各种格式的文件内容提取的库. 将上传文件至服务器,进行解析文件时,经常需要判断文件是否损坏.我们可以使用tika来检测文件是否损坏 maven引入如下: &l ...
- Tika提取文件元数据
Tika可以从文件中提取元数据. 什么是元数据: 元数据是文件所提供的的附件信息即文件的属性. word文档的元数据: Tika提取元数据: 我们可以使用文件parse()方法提取元数据,传递一个空的 ...
- Linux命令-文件文本操作grep
文件文本操作 grep 在文件中查找符合正则表达式条件的文本行 cut 截取文件中的特定字段 paste 附加字段 tr 字符转换或压缩 sort 调整文本行的顺序,使其符合特定准则 uniq 找出重 ...
随机推荐
- DevExpress中GridControl控件焦点改变时触发事件
FocusedRowObjectChanged 事件.可以在焦点改变一行的时候触发对应的事件. 做一个记录 大家如果有问题可以 Console.WriteLine("加群"+&qu ...
- 周立功DTU+温度传感器,ZWS物联网平台尝试
1.前言 了解到周立功有相关的物联网云平台,近期在调研动态环境监控项目,可以进行一个上云的尝试.购置了传感器.周立功的DTU等硬件,将传感器的温度.湿度等数据进行一个云平台的上传. 2.前期准备 传感 ...
- form enctype="multipart/form-data" ajax 文件上传
<form method="post" enctype="multipart/form-data" id="resource"> ...
- Linux 中的文件简单说明
Linux 中的文件简单说明 作者:Grey 原文地址: 博客园:Linux 中的文件简单说明 CSDN:Linux 中的文件简单说明 说明 本文基于 CentOS 7 根目录(/)下文件夹主要作用 ...
- python文件名解析---从文件名获得分类类别
python文件名解析-从文件名获得分类类别 python os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表.列表以字母顺序. listdir()方法 举例说明 lis ...
- Java 中的接口还可以这样用,你知道吗?
Java 程序员都知道要面向接口编程,那 Java 中的接口除了定义接口方法之外还能怎么用你知道吗?今天阿粉就来带大家看一下 Java 中的接口还可以有哪些用法. 基本特性 我们先看一下接口的基本特性 ...
- 安装es可视化软件Kibana
一 Kibana介绍 Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作. 您.可以使用 Kibana 对 Ela ...
- 编译安装nmon
nmon 是什么? nmon(Nigel's performance Monitor for Linux)是一种Linux性能监视工具,当前它支持 Power/x86/x86_64/Mainframe ...
- buuctf_Dest0g3_crypto
babyAES: 题目如下: from Crypto.Cipher import AES import os iv = os.urandom(16) key = os.urandom(16) my_a ...
- sqlSession封装以及CRUD的实现
sqlSession封装以及CRUD的实现 封装MyBatisUtil dao 定义方法 映射文件写sql语句 daoimpl实现类 实现方法 test类测试方法 整体结构