所以我有一个要构建的任务,它将把我们数据库中的大量数据存档为JSON。
让您更好地了解正在发生的事情; X有100s的Ys,Y有100s的Zs,依此类推。我正在为每个X,Y和Z创建一个json文件。但是每个X json文件都有一个子数组,其中包含X的子Ys,同样Ys存储了一个子Zs数组..
它比许多情况下的情况更复杂,但你应该从我认为的那个例子中了解复杂性。
我使用的是ColdFusion,但这对于此任务似乎是一个糟糕的选择,因为它因内存错误而崩溃。在我看来,如果它正在从运行任务时不再引用的内存中删除查询(即:垃圾收集),那么任务应该有足够的内存,但是根据ColdFusion根本没有进行任何垃圾收集,并且必须在请求完成后执行此操作。
因此,我正在寻找有关如何更好地完成我在CF中的任务的建议,或者寻求有关其他语言的建议。
感谢。
答案 0 :(得分:5)
1)如果启用了调试,coldfusion将继续查询,直到页面完成。把它关掉!
2)您可能需要structDelete()查询变量以允许它被垃圾收集,否则只要存在引用它的作用域,它就可能持久存在。例如。,
<cfset structDelete(variables,'myQuery') />
3)cfquery将整个ResultSet拉入内存。大多数时候这很好。但是对于大型结果集的报告,您不希望这样。一些JDBC驱动程序支持设置fetchSize,它以前向只读方式允许您一次获得一些结果。这样,您可以处理成千上万行,而不会淹没内存。我只用了不到100mb的堆就在~80秒内生成了一个1GB的csv文件。这需要退出Java。但它一石二鸟。它减少了JDBC驱动程序一次带入的数据量,并且由于您直接使用ResultSet,因此不会遇到提到的cfloop问题@orangepips。当然,不适合没有Java杂志的人。
你可以这样做(你的构建路径中需要cfusion.jar):
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import au.com.bytecode.opencsv.CSVWriter;
import coldfusion.server.ServiceFactory;
public class CSVExport {
public static void export(String dsn,String query,String fileName) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
FileWriter fw = null;
BufferedWriter bw = null;
try {
DataSource ds = ServiceFactory.getDataSourceService().getDatasource(dsn);
conn = ds.getConnection();
// we want a forward-only, read-only result.
// you may want need to use a PreparedStatement instead.
stmt = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY
);
// we only want to go forward!
stmt.setFetchDirect(ResultSet.FETCH_FORWARD);
// how many records to pull back at a time.
// the hard part is balancing memory usage, and round trips to the database.
// basically sacrificing speed for a lower memory hit.
stmt.setFetchSize(256);
rs = stmt.executeQuery(query);
// do something with the ResultSet, for example write to csv using opencsv
// the key is to stream it. you don't want it stored in memory.
// so excel spreadsheets and pdf files are out, but text formats like
// like csv, json, html, and some binary formats like MDB (via jackcess)
// that support streaming are in.
fw = new FileWriter(fileName);
bw = new BufferedWriter(fw);
CSVWriter writer = new CSVWriter(bw);
writer.writeAll(rs,true);
}
catch (Exception e) {
// handle your exception.
// maybe try ServiceFactory.getLoggingService() if you want to do a cflog.
e.printStackTrace();
}
finally() {
try {rs.close()} catch (Exception e) {}
try {stmt.close()} catch (Exception e) {}
try {conn.close()} catch (Exception e) {}
try {bw.close()} catch (Exception e) {}
try {fw.close()} catch (Exception e) {}
}
}
}
弄清楚如何传递参数,记录,将其转换为后台进程(提示:扩展线程)等是不同的问题,但是如果你理解这些代码,那就不应该太难了。
4)或许看Jackson生成你的json。它支持streaming,并结合fetchSize和BufferedOutputStream,你应该能够保持内存使用率下降。
答案 1 :(得分:4)
Eric,在请求结束和I've documented it fairly extensively in another SO question之前,您对ColdFusion垃圾回收没有从内存中删除查询信息是绝对正确的。简而言之,当您循环查询时,您会点击OoM Exceptions。您可以使用VisualVM之类的工具来证明它,以便在进程运行时生成堆转储,然后通过Eclipse Memory Analyzer Tool(MAT)运行生成的转储。 MAT将向您展示的是一个大型层次结构,从一个名为(我没有做到这一点)CFDummyContent
的对象开始,其中包含对cfquery
和{{1}的引用。 }标签。注意,尝试将其更改为存储过程甚至通过JDBC进行数据库交互并没有什么不同。
因此。什么。至。怎么办?
我花了一段时间才弄明白,但你有三个选项可以提高复杂程度:
cfqueryparam
使用cfthread看起来像这样:
<cthread/>
注意,此代码不利用异步处理,因此每次线程调用后立即连接,而是cfthread在其自己的请求范围内运行的副作用,与页面无关。
我不会在这里覆盖ColdFusion网关。 HTTP菊花链表示执行工作的增量,并在增量结束时启动对同一算法的请求,告诉它执行下一个增量。
基本上,所有这三种方法都允许在进程中收集这些内存引用。
是的,对于任何要求的人,已经提出过Adobe的错误,请参阅引用的问题。此外,我认为此问题特定于Adobe ColdFusion,但尚未测试Railo或OpenDB。
最后,不得不咆哮。我花了很多时间跟踪这个,将它修复到我自己的大型代码库中,并且引用的问题中列出的其他几个也是如此。 AFAIK Adobe并没有承认这个问题,更不用说修复它了。并且,是的,它是一个简单而简单的错误。