我需要另外一套眼睛。
我已经使用这个确切的代码将一个zip文件写入数百GB,而MacOSX上没有本地修改。
如果100%未更改的代码刚刚部署到运行Ubuntu的AWS实例,则相同的代码会遇到Out of Memory问题(堆空间)。
以下是正在运行的代码,将MyBatis流式传输到磁盘上的CSV文件:
File directory = new File(feedDirectory);
File file;
try {
file = File.createTempFile(("feed-" + providerCode + "-"), ".csv", directory);
} catch (IOException e) {
throw new RuntimeException("Unable to create file to write feed to disk: " + e.getMessage(), e);
}
String filePath = file.getAbsolutePath();
log.info(String.format("File name for %s feed is %s", providerCode, filePath));
// output file
try (FileOutputStream out = new FileOutputStream(file)) {
streamData(out, providerCode, startDate, endDate);
} catch (IOException e) {
throw new RuntimeException("Unable to write feed to file: " + e.getMessage());
}
public void streamData(OutputStream outputStream, String providerCode, Date startDate, Date endDate) throws IOException {
try (CSVPrinter printer = CsvUtil.openPrinter(outputStream)) {
StreamingHandler<FStay> handler = stayPrintingHandler(printer);
warehouse.doForAllStaysByProvider(providerCode, startDate, endDate, handler);
}
}
private StreamingHandler<FStay> stayPrintingHandler(CSVPrinter printer) {
StreamingHandler<FStay> handler = new StreamingHandler<>();
handler.setHandler((stay) -> {
try {
EXPORTER.writeStay(printer, stay);
} catch (IOException e) {
log.error("Issue with writing output: " + e.getMessage(), e);
}
});
return handler;
}
// The EXPORTER method
import org.apache.commons.csv.CSVPrinter;
public void writeStay(CSVPrinter printer, FStay stay) throws IOException {
List<Object> list = asList(stay);
printer.printRecord(list);
}
List<Object> asList(FStay stay) {
List<Object> list = new ArrayList<>(46);
list.add(stay.getUid());
list.add(stay.getProviderCode());
//....
return list;
}
这是我在本地运行时的JVM堆空间图(使用jvisualvm)。我在本地运行Java 8(jdk1.8.0_51和1.8.0_112)并获得了很好的结果。甚至写出了数TB的数据。
^在上面,最大堆空间设置为4 gigs,并且它最多增加到1.5 gigs,然后再回到500 MB左右,同时将数据流式传输到CSV文件,因为它&#39;应该是。
但是,当我使用jdk 1.8.0_111在Ubuntu上运行时,完全相同的操作将无法完成,堆空间不足(java.lang.OutOfMemoryError:Java堆空间)
我已经将Xmx值从8演出提升到了16到25演出,但仍然没有堆空间。同时......文件的总大小总共只有10个Gigs ...这真让我感到困惑。
这里是JVisualVm图在Ubuntu框中的样子:
毫无疑问,它是在两种环境中运行的完全相同的代码,在每个环境中执行相同的操作(提供相同数据的相同数据库服务器)
此时我能想到的唯一区别是:
任何人都可以帮助解决这个问题吗?
更新
我已经尝试更换所有“尝试使用资源”&#39;带有显式刷新/关闭语句且没有运气的语句。
更重要的是,当我开始看到数据进入时,我试图在Ubuntu盒子上强制进行垃圾收集,但它没有任何效果 - 肯定会阻止堆被收集在Ubuntu机器上...在OS X上运行完全相同的代码让我再次编写完整的辣酱玉米饼馅没问题。
更新2
除了上述环境的差异之外,我能想到的唯一其他差异是,如果AWS中的服务器之间的连接如此之快,以至于它比将数据刷新到磁盘更快地流式传输数据......但这仍然无法解释我总共只有10演出数据的问题,并且它会耗尽20 Gigs的堆空间。
Ubuntu / Java级别是否有可能存在此错误?
更新3
尝试替换CSVPrinter
的输出以使用完全独立的库(OpenCSV&#CSV; CSVWriter代替Apache的CSV库),并且会出现相同的结果。
一旦这段代码开始从数据库接收数据,堆开始爆炸,垃圾收集器无法回收任何内存......但仅限于Ubuntu。在OS X上,一切都立即回收,堆永远不会增长。
我在每次写作后都试过冲洗流,但也没有运气。
更新4
打开堆转储,根据这个我应该看看数据库驱动程序。特别是亚马逊的红移驱动程序中的InboundDataHandler。
我使用myBatis和自定义结果处理程序。我尝试将结果处理程序设置为在获得结果时有效地执行任何操作(新的ResultHandler&lt;&gt;(){//方法被覆盖以实际上没有任何内容})并且我知道我没有坚持任何引用。
因为它是由AWS / Redshift定义的InboundDataHandler ......它让我觉得它可能低于myBatis级别......:
我在这里设置我的SqlSessionFactoryBean:
@Bean
public javax.sql.DataSource redshiftDataSource() throws ClassNotFoundException {
log.info("Got to datasource config");
// Dynamically load driver at runtime.
Class.forName(dataWarehouseDriver);
DataSource dataSource = new DataSource();
dataSource.setURL(dataWarehouseUrl);
dataSource.setUserID(dataWarehouseUsername);
dataSource.setPassword(dataWarehousePassword);
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws ClassNotFoundException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(redshiftDataSource());
return factoryBean;
}
这是myBatis代码我作为测试运行,以验证它不是我保留在ResultHandler中的记录:
warehouse.doForAllStaysByProvider(providerCode, startDate, endDate, new ResultHandler<FStay>() {
@Override
public void handleResult(ResultContext<? extends FStay> resultContext) {
// do nothing
}
});
有没有办法可以强制SQL连接不挂在记录上?我再次在我的本地机器上重新进行迭代,这个内存泄漏没有问题......只有在托管AWS环境中运行代码时才会出现问题。在这两种情况下,数据库驱动程序和服务器都是相同的。
更新6 我认为它终于得到了解决。感谢所有指向我的堆转储方向。这有助于将其缩小到令人讨厌的阶级。
之后,我对AWS redshift驱动程序进行了一些研究,它明确表示您的客户应该为大数据的任何操作指定限制。所以我在myBatis配置中找到了如何做到这一点:
<select id="doForAllStaysByProvider" fetchSize="1000" resultMap="FStayResultMap">
select distinct
f_stay.uid,
这就是诀窍。
请注意,即使处理从AWS远程下载的大型数据集(AWS中的数据库,在家中的笔记本电脑上执行的代码),这也是不必要的,而且这不是必要的,因为我&#39 ; m重写myBatis ResultHandler&lt;&gt;它可以单独处理每一行,也不会保留任何对象。
AWS redshift jdbc驱动程序只有在AWS(aws中的数据库,AWS实例中执行的代码)中运行时才会发生一些时髦的事情,导致此InboundDataHandler永远不会释放其资源,除非指定了fetchSize。
这里是现在运行的服务器堆,比以前在AWS中更进一步,堆空间永远不会超过500Mb,并且在我点击强制gc&#39;在jvisualvm中,它显示了使用过的&#39;堆小于100mb:
再次感谢所有帮助指导此事的人!
答案 0 :(得分:3)
终于找到了解决方案。
堆转储是最大的帮助 - 它表明亚马逊的RedShift / postgres JDCB驱动程序的InboundDataHandler
类是罪魁祸首。
设置SqlSession的代码看似合法,所以前往Amazon's documentation登陆了这个宝石:
在检索大数据时避免客户端内存不足错误 使用JDBC进行设置,您可以使客户端批量获取数据 通过设置JDBC提取大小参数。
我们之前没有遇到过这种情况,因为我们在MyBatis中使用自定义ResultHandlers
流式传输结果...但是当AWS Redshift JDBC驱动程序在AWS本身上运行而不是在AWS连接之外时,似乎有些不同英寸
根据文档中的指导,我们在MyBatis选择查询中添加了'fetchSize':
<select id="doForAllStaysByProvider" fetchSize="1000" resultMap="FStayResultMap">
select distinct
f_stay.uid,
瞧!一切都在游泳。这是我们做出的唯一改变,堆永远不会超过几百MB。
您可以在上面的图表中看到堆离开图表的情况,只要数据开始在亚马逊上接收,堆就会线性上升,并且一旦启动就不会回收一盎司的堆空间。
我的猜测是Redshift JDBC驱动程序在Amazon的环境中进行某种优化时会做一些不同的事情...这就是我能想到解释行为的全部内容。
显然亚马逊知道自从他们提前记录之后发生了什么。我可能不知道发生了什么的充分“原因”,但至少一切都以一种令人满意的方式解决了。
感谢所有帮助过的人。