【问题标题】:Getting CPU 100 percent when I am trying to downloading CSV in Spring当我尝试在 Spring 中下载 CSV 时获得 100% 的 CPU
【发布时间】:2016-07-16 19:36:59
【问题描述】:

当我尝试在我的项目中下载 CSV 时,我在服务器上遇到 CPU 性能问题,CPU 达到 100%,但 SQL 在 1 分钟内返回响应。在 CSV 中,我们为一个用户写入了大约 60 万条记录,它工作正常,但对于并发用户,我们遇到了这个问题。

环境

  • 春季 4.2.5
  • Tomcat 7/8(已分配 2GB 内存)
  • MySQL 5.0.5
  • Java 1.7

这是 Spring Controller 代码:-

@RequestMapping(value="csvData")
public void getCSVData(HttpServletRequest request, 
        HttpServletResponse response,
        @RequestParam(value="param1", required=false) String param1,
        @RequestParam(value="param2", required=false) String param2,
        @RequestParam(value="param3", required=false) String param3) throws IOException{

    List<Log> logs =   service.getCSVData(param1,param2,param3);

    response.setHeader("Content-type","application/csv");
    response.setHeader("Content-disposition","inline; filename=logData.csv");
    PrintWriter out = response.getWriter(); 
    out.println("Field1,Field2,Field3,.......,Field16");
    for(Log row: logs){
            out.println(row.getField1()+","+row.getField2()+","+row.getField3()+"......"+row.getField16());
    }
    out.flush();  
    out.close(); 
}}

持久化代码:-我使用的是spring JDBCTemplate

@Override
public List<Log> getCSVLog(String param1,String param2,String param3) {
    String sql =SqlConstants.CSV_ACTIVITY.toString();
    List<Log> csvLog = JdbcTemplate.query(sql, new Object[]{param1, param2, param3},
            new RowMapper<Log>() {
                @Override
                public Log mapRow(ResultSet rs, int rowNum)
                throws SQLException {
                    Log log = new Log();
                    log.getField1(rs.getInt("field1"));
                    log.getField2(rs.getString("field2"));
                    log.getField3(rs.getString("field3"));
                    .
                    .
                    .
                    log.getField16(rs.getString("field16"));
                    }
                 return log;
                }
            });

    return csvLog;
}

【问题讨论】:

  • 您应该提供您的持久性代码 - 如果 SQL 在 1 分钟后返回响应,我相信瓶颈在数据库中,而不是在 Java 中
  • Cootri,使用 StringBuilder 后,我收到 HTTP 状态 500 - 处理程序处理失败;嵌套异常是 java.lang.OutOfMemoryError: GC 开销限制超出
  • 如果您设法将问题缩小到 DB 调用,那么您包含的代码就无关紧要了。没有任何方法可以帮助诊断当前问题中的问题。
  • @NikhilGupta,您使用的是 SAX 解析器还是 DOM 解析器?请使用 SAX 解析器。
  • @PraveenKumar,我正在使用 JDBCTemplate 并获取 ResultSet。

标签: java mysql spring spring-mvc csv


【解决方案1】:

我认为您需要具体说明“100% CPU 使用率”的含义,无论是 Java 进程还是 MySQL 服务器。由于您有 600K 记录,因此尝试将所有内容加载到内存中很容易以 OutOfMemoryError 结束。鉴于这适用于一个用户,这意味着您有足够的堆空间来为一个用户处理这么多的记录,并且当有多个用户尝试使用同一个服务时,症状就会出现。

我在您发布的代码中看到的第一个问题是您尝试将所有内容加载到一个大列表中,并且列表的大小根据Log 类的内容而有所不同。使用这样的列表还意味着您必须有足够的内存来处理 JDBC 结果集并生成Log 实例的新列表。对于越来越多的用户来说,这可能是一个主要问题。这种类型的短期对象会导致频繁的 GC,一旦 GC 跟不上正在收集的垃圾量,它显然会失败。为了解决这个主要问题,我的建议是使用ScrollableResultSet。此外,您可以将此结果集设为只读,例如下面是用于创建可滚动结果集的代码片段。查看文档以了解如何使用它。

Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);

如果您使用的是纯 JDBC 或 SpringJDBC 模板,则上述选项适用。如果您的项目中已经使用了 Hibernate,您仍然可以使用以下代码片段实现相同的目的。请再次查看文档以获取更多信息,并且您有不同的 JPA 提供程序。

StatelessSession session = sessionFactory.openStatelessSession();
Query query = session.createSQLQuery(queryStr).setCacheable(false).setFetchSize(Integer.MIN_VALUE).setReadOnly(true);
query.setParameter(query_param_key, query_paramter_value);
ScrollableResults resultSet = query.scroll(ScrollMode.FORWARD_ONLY);

这样,您不会一次性将所有记录加载到 Java 进程中,而是按需加载它们,并且在任何给定时间都将占用很小的内存。请注意,在您处理完整个记录集之前,JDBC 连接将处于打开状态。这也意味着如果许多用户要从该端点下载 CSV 文件,您的数据库连接池可能会耗尽。您需要采取措施来解决这个问题(即使用 API 管理器来限制对该端点的调用、从只读副本读取或任何可行的选项)。

我的另一个建议是流式传输您已经完成的数据,以便在处理下一组记录之前处理从数据库获取的任何记录并将其发送到客户端。我再次建议您使用 CSV 库(例如 SuperCSV)来处理此问题,因为这些库旨在处理大量数据。

请注意,此答案可能无法完全回答您的问题,因为您没有提供来源的必要部分,例如如何从数据库中检索数据,但会给出解决此问题的正确方向

【讨论】:

  • Bunti,感谢更新,请查看我的持久化代码
  • @NikhilGupta 我可以看到您的选择是使用 1.(如上所示)DataSource 对象来获取连接并将fetchSize 设置为 Integer.MIN_VALUE 作为提示到 JDBC 驱动程序使用记录流而不是获取一组一定大小的记录。 2.) 是继承 JDBCTemplate 或 NamedParamterJDBCTemplate 并覆盖 getPreparedStatementCreator 方法以提供您自己的 PreparedStatement 流记录。
【解决方案2】:

您在一次从数据库加载应用程序服务器上的所有数据时遇到问题,尝试使用limitoffset 参数运行查询(带有强制order by),将加载的记录推送到客户端并加载下一部分数据不同的offset。它可以帮助您减少内存占用,并且不需要一直保持与数据库的连接。当然,数据库会加载更多,但也许整个情况会更好。尝试不同的limit 值,例如 5K-50K 并监控应用服务器和数据库上的 cpu 使用情况。

如果您可以允许保持与数据库的许多打开连接,@Bunti 答案非常好。

http://dev.mysql.com/doc/refman/5.7/en/select.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-08
    • 1970-01-01
    • 2022-06-14
    • 2019-10-14
    • 2011-07-12
    • 2022-10-04
    • 1970-01-01
    • 2014-05-24
    相关资源
    最近更新 更多