背景

笔者曾供职于某信息安全公司,接到过一个需求,提取文档中的文本以供后续分析。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解析文件文本导致的内存溢出问题的更多相关文章

  1. 解决 SqlServer执行脚本,文件过大,内存溢出问题

    原文:解决 SqlServer执行脚本,文件过大,内存溢出问题 执行.sql脚本文件,如果文件较大时,执行会出现内存溢出问题,可用命令替代 cmd 中输入 osql -S 127.0.0.1,8433 ...

  2. 文件上传之--内存溢出(System.OutOfMemoryException)

    两周前就想把这点经验记录下来了,由于拖延症上身,直到刚才突然想起这件未完成的任务,今天是1024,在这个特别的日子里,祝所有程序猿兄弟姐妹们节日快乐! 上传功能一直很正常,直到上传了个500多兆的文件 ...

  3. java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架

    产品需求 产品经理需要导出一个页面的所有的信息到 EXCEL 文件. 需求分析 对于 excel 导出,是一个很常见的需求. 最常见的解决方案就是使用 poi 直接同步导出一个 excel 文件. 客 ...

  4. 小程序里面使用wxParse解析富文本导致页面空白等

    在部分安卓手机上会出现白屏的情况且有些ios手机上图文混排上,图片显示不出问题 解决:把插件里面的console.dir去掉即可(原因在于安卓手机无法解析console.dir) 有些图片解析出来下面 ...

  5. 记一次Orika使用不当导致的内存溢出

    hprof 文件分析 2021-08-24,订单中心的一个项目出现了 OOM 异常,使用 MemoryAnalyzer 打开 dump 出来的 hprof 文件,可以看到 91.27% 的内存被一个超 ...

  6. .net向文件写入字符串流内存溢出的问题

    字符串过大导致抛出异常: exceptopm of type 'system.outOfmemoryexception' was thrown 解决方法:逐块写入可以避免这个问题

  7. 【apache tika】apache tika获取文件内容(与FileUtils的对比)

    Tika支持多种功能: 文档类型检测 内容提取 元数据提取 语言检测 重要特点: 统一解析器接口:Tika封装在一个单一的解析器接口的第三方解析器库.由于这个特征,用户逸出从选择合适的解析器库的负担, ...

  8. apache tika检测文件是否损坏

    Apache Tika用于文件类型检测和从各种格式的文件内容提取的库. 将上传文件至服务器,进行解析文件时,经常需要判断文件是否损坏.我们可以使用tika来检测文件是否损坏 maven引入如下: &l ...

  9. Tika提取文件元数据

    Tika可以从文件中提取元数据. 什么是元数据: 元数据是文件所提供的的附件信息即文件的属性. word文档的元数据: Tika提取元数据: 我们可以使用文件parse()方法提取元数据,传递一个空的 ...

  10. Linux命令-文件文本操作grep

    文件文本操作 grep 在文件中查找符合正则表达式条件的文本行 cut 截取文件中的特定字段 paste 附加字段 tr 字符转换或压缩 sort 调整文本行的顺序,使其符合特定准则 uniq 找出重 ...

随机推荐

  1. js判断数组中是否有重复数据

    var arr=[1,3,5,7,9,9,10,10,11,12,34,3,6,92,1]; var tempbool = false; //默认无重复 for (let index = 0; ind ...

  2. 生成requirements.txt

    requirements.txt文件 requirements.txt 文件是项目的依赖包及其对应版本号的信息列表,即记载你这个项目所安装的依赖. 作用:用来重新构建项目或者记录项目所需要的运行环境依 ...

  3. 数电第11周周结_by_yc

    Lab7_时序逻辑验证 一.简易电子时钟 功能描述:   设计一简易电子时钟,支持时.分.秒显示,其中HEX7-HEX6显示时,HEX5-HEX4显示分,HEX1-HEX0显示秒,假设进制为:18秒= ...

  4. Scanner例题讲解

    Scanner例题讲解 题:输入多个平均数,求其总和与平均数;每输入一个数用回车确认,通过输入非数字来结束输入并输出执行结果  public class Demo05 {     //输入多个平均数, ...

  5. 微软宣布 S2C2F 已被 OpenSSF 采用

    开源供应链安全对大多数 IT 领导者来说是个日益严峻的挑战,围绕确保开发人员在构建软件时如何使用和管理开源软件 (OSS) 依赖项的稳健策略至关重要.Microsoft 发布安全供应链消费框架 (S2 ...

  6. SQLMap入门——获取数据库中的表名

    查询完数据库后,查询指定数据库中所有的表名 python sqlmap.py -u http://localhost/sqli-labs-master/Less-1/?id=1 -D xssplatf ...

  7. vue3+TS 自定义指令:长按触发绑定的函数

    vue3+TS 自定义指令:长按触发绑定的函数 而然间看到一个在vue2中写的长按触发事件的自定义指定,想着能不能把他copy到我的vue3项目中呢. 编写自定义指令时遇到的几个难点 1.自定义指令的 ...

  8. [python]《Python编程快速上手:让繁琐工作自动化》学习笔记6

    1. 发送电子邮件和短信笔记(第16章)(代码下载) 1.1 发送电子邮件 简单邮件传输协议(SMTP)是用于发送电子邮件的协议.SMTP 规定电子邮件应该如何格式化.加密.在邮件服务器之间传递,以及 ...

  9. Hadoop详解(08) - Hadoop企业优化方案.docx

    Hadoop详解(08) - Hadoop企业优化方案.docx MapReduce优化 MapReduce 跑的慢的原因 计算机性能:CPU.内存.磁盘健康.网络 I/O 操作优化 (1)数据倾斜 ...

  10. 基于Linux下的Ubuntu操作系统常用命令

    一 .linux操作系统的特点 1.linux下一切皆文件 2.linux系统就像一个倒置数 3.linux系统支持多用户.多任务 二. Ubuntu --"乌班图"操作系统 Ub ...