Java 使用Apache Camel插入大型CSV文件时出现GC问题

Java 使用Apache Camel插入大型CSV文件时出现GC问题,java,csv,garbage-collection,apache-camel,large-files,Java,Csv,Garbage Collection,Apache Camel,Large Files,我有一个大的CSV文件,包含大约5.2M行。我想解析这个文件并将数据插入数据库。我正在为此使用ApacheCamel 路线相当简单(本例简化) bindyCustomer是CSV文件和 CustomerProcessor是一个处理器,它将Bindy客户对象的数据作为SQL插入的对象数组返回。实际对象有39个字段(如上所述) 对于前800000到1000000条线路,这一切正常,但随后就停止了 我已经用JVisualVM和VisualGC插件监控了camel实例,我可以看到旧一代已经满了,当它达到

我有一个大的CSV文件,包含大约5.2M行。我想解析这个文件并将数据插入数据库。我正在为此使用ApacheCamel

路线相当简单(本例简化)

bindyCustomer是CSV文件和 CustomerProcessor是一个处理器,它将Bindy客户对象的数据作为SQL插入的对象数组返回。实际对象有39个字段(如上所述)

对于前800000到1000000条线路,这一切正常,但随后就停止了

我已经用JVisualVM和VisualGC插件监控了camel实例,我可以看到旧一代已经满了,当它达到最大值时,整个系统就会停止,但不会崩溃。 在这一点上,老一代已经满了,伊甸园空间几乎满了,两个幸存者空间都是空的(因为我猜它无法将任何东西移动到老一代)

那么这里出了什么问题?在我看来,这就像骆驼SQL组件中的内存泄漏。 数据主要存储在ConcurrentHashMap对象中

当我取出SQL组件时,旧一代几乎无法填充

我正在使用Camel 2.15.1,我将尝试使用2.17.1,看看这是否解决了问题


更新:我尝试了Camel 2.17.1(同样的问题),并尝试使用Java.sql.Statement.executeUPdate在Java中插入dotheinsert。使用此选项,我成功地插入了大约2.6 M行,但随后它也停止了。有趣的是我没有记错。它只是停止了。

我没有测试您的代码,但是,我确实注意到您的第二个split语句没有流式传输。我建议你试试。如果您有太多的并行工作流,GC可能会在您释放资源之前填满,而这些资源会将您锁定。SQL语句所花费的时间可能是允许GC获得太多构建时间的原因,因为您正在并行化主处理

from("file:work/customer/").id("customerDataRoute")
    .split(body().tokenize("\n")).streaming().parallelProcessing()
        .unmarshal(bindyCustomer)
        .split(body()).streaming() //Add a streaming call here and see what happens
            .process(new CustomerProcessor())
            .to("sql:INSERT INTO CUSTOMER_DATA(`FIELD1`,`FIELD2`) VALUES(#,#)");

我没有测试您的代码,但是,我注意到您的第二个split语句不是流式的。我建议你试试。如果您有太多的并行工作流,GC可能会在您释放资源之前填满,而这些资源会将您锁定。SQL语句所花费的时间可能是允许GC获得太多构建时间的原因,因为您正在并行化主处理

from("file:work/customer/").id("customerDataRoute")
    .split(body().tokenize("\n")).streaming().parallelProcessing()
        .unmarshal(bindyCustomer)
        .split(body()).streaming() //Add a streaming call here and see what happens
            .process(new CustomerProcessor())
            .to("sql:INSERT INTO CUSTOMER_DATA(`FIELD1`,`FIELD2`) VALUES(#,#)");

好吧,我知道这里出了什么问题。与插入部分相比,读取部分基本上太快了。这个例子有点过于简单,因为在读取和插入之间有一个seda队列(因为我必须对示例中未显示的内容进行选择)。 但即使没有轿车排队,它也永远不会结束。当我杀死骆驼时,我意识到了问题所在,并得到了一条信息,那就是飞机上还有几千条信息

因此,当插入端无法跟上时,使用并行处理进行读取是没有意义的

from("file:work/customer/").id("customerDataRoute")
        .onCompletion().log("Customer data  processing finished").end()
        .log("Processing customer data ${file:name}")
        .split(body().tokenize("\n")).streaming() //no more parallel processing
        .choice()
            .when(simple("${body} contains 'HEADER TEXT'")) //strip out the header if it exists
            .log("Skipping first line")
            .endChoice()
        .otherwise()
            .to("seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true")
            .endChoice();


from("seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true")
            .unmarshal(bindyCustomer)
            .split(body())
            .process(new CustomerProcessor()).id("CustomProcessor") //converts one Notification into an array of values for the SQL insert
.to("sql:INSERT INTO CUSTOMER_DATA(`FIELD1`,`FIELD2`) VALUES(#,#)");
我在SEDA队列上定义了一个大小(默认情况下不受限制),并在队列已满时设置调用线程块

seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true
并行处理通过使用SEDA队列上的20个并发使用者来完成。请注意,无论出于什么原因,您在调用路由时都必须指定队列大小(而不仅仅是在您定义它的地方)


现在,内存消耗很小,它可以毫无问题地插入500万条记录。

好的,我知道这里出了什么问题。与插入部分相比,读取部分基本上太快了。这个例子有点过于简单,因为在读取和插入之间有一个seda队列(因为我必须对示例中未显示的内容进行选择)。 但即使没有轿车排队,它也永远不会结束。当我杀死骆驼时,我意识到了问题所在,并得到了一条信息,那就是飞机上还有几千条信息

因此,当插入端无法跟上时,使用并行处理进行读取是没有意义的

from("file:work/customer/").id("customerDataRoute")
        .onCompletion().log("Customer data  processing finished").end()
        .log("Processing customer data ${file:name}")
        .split(body().tokenize("\n")).streaming() //no more parallel processing
        .choice()
            .when(simple("${body} contains 'HEADER TEXT'")) //strip out the header if it exists
            .log("Skipping first line")
            .endChoice()
        .otherwise()
            .to("seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true")
            .endChoice();


from("seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true")
            .unmarshal(bindyCustomer)
            .split(body())
            .process(new CustomerProcessor()).id("CustomProcessor") //converts one Notification into an array of values for the SQL insert
.to("sql:INSERT INTO CUSTOMER_DATA(`FIELD1`,`FIELD2`) VALUES(#,#)");
我在SEDA队列上定义了一个大小(默认情况下不受限制),并在队列已满时设置调用线程块

seda:processCustomer?size=40&concurrentConsumers=20&blockWhenFull=true
并行处理通过使用SEDA队列上的20个并发使用者来完成。请注意,无论出于什么原因,您在调用路由时都必须指定队列大小(而不仅仅是在您定义它的地方)


现在,内存消耗非常小,它可以毫无问题地插入500万条记录。

谢谢您的提示。我试过了,但没有解决问题。.unmarchal(bindyCustomer)只返回一个包含一个元素的数组,因此在这种情况下,流不应该有太大的区别。你能想出其他可能出错的事情吗?我将尝试用Java进行插入,看看这是否解决了问题。嗯,我有一些粗略的猜测。您是否能够将id标记添加到路由中,然后打开JConsole以确认所有线程的“挂起”位置?路由已作为id(customerDataRoute)存在,或者您正在引用其他内容?您可以将id添加到路由中的每个组件。这样,您自动创建的MBean将具有该id。下面是一个快速教程,帮助您了解我所说的内容:我必须在配置中遗漏一些内容,但没有创建MBean。JConsole没有列出任何apache MBean。谢谢您的提示。我试过了,但没有解决问题。.unmarchal(bindyCustomer)只返回一个包含一个元素的数组,因此在这种情况下,流不应该有太大的区别。你能想出其他可能出错的事情吗?我将尝试用Java进行插入,看看这是否解决了问题。嗯,我有一些粗略的猜测。您是否可以将id标记添加到路由中,然后打开JConsole以确认所有线程的“挂起”位置?路由已作为id(customerDataRoute)存在,或者您正在引用其他内容?您可以添加id