Java 使用Apache Camel插入大型CSV文件时出现GC问题
我有一个大的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,看看这是否解决了问题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实例,我可以看到旧一代已经满了,当它达到
更新:我尝试了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