2016-03-29 61 views
1

當我嘗試在我的項目中下載CSV時,CPU在服務器上出現CPU性能問題,CPU變爲100%,但SQL在1分鐘內返回響應。在CSV中,我們爲一個用戶編寫了大約600K條記錄,但它對於併發用戶來說工作正常,但我們遇到了這個問題。當我嘗試在春季下載CSV時獲得CPU 100%

環境

  • 春4.2.5
  • Tomcat的7/8(2GB內存分配)
  • 的MySQL 5.0.5
  • 的Java 1.7

這裏是彈簧控制器代碼: -

@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; 
} 
+1

您應提供持久性的代碼 - 如果SQL返回性反應,1分鐘後我認爲瓶頸是在數據庫中,而不是在Java中 – Cootri

+0

Cootri,使用StringBuilder的後我收到HTTP狀態500 - 處理程序處理失敗;嵌套的異常是java.lang.OutOfMemoryError:超出GC開銷限制 –

+0

如果您設法將問題縮小到數據庫調用,那麼您包含的代碼是不相關的。沒有什麼能夠幫助診斷目前問題中的問題。 – kryger

回答

2

我認爲你必須是具體的,你的「100%的CPU使用率」的意思無論是Java進程或MySQL服務器。由於您有600K條記錄,因此嘗試將所有內容加載到內存中將很容易在OutOfMemoryError中結束。由於這對一個用戶有效,意味着只有一個用戶擁有足夠的堆空間來處理這些記錄,並且當有多個用戶嘗試使用相同的服務時,表面會出現症狀。

我可以在您的發佈代碼中看到的第一個問題是您嘗試將所有內容加載到一個大列表中,並且列表的大小根據Log類的內容而有所不同。使用這樣的列表也意味着您必須有足夠的內存來處理JDBC結果集並生成新的實例列表。這對於越來越多的用戶來說可能是一個主要問題。這種短壽命物體會導致頻繁的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來處理這些庫,因爲這些庫旨在處理好數據加載。

請注意,這個答案可能並不完全回答你的問題,你沒有提供諸如如何從DB數據源的必要部分,但會給出正確的方向來解決這個問題

+0

Bunti,感謝您的更新,請參閱我的持久性代碼 –

+0

@NikhilGupta您可以看到的選項是使用1.(如上所示)a [DataSource](https://docs.oracle.com/javase/7 /docs/api/javax/sql/DataSource.html)對象來獲取連接並設置[fetchSize](https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html #setFetchSize(int))轉換爲Integer.MIN_VALUE,作爲提示JDBC驅動程序使用記錄流而不是獲取一定大小的記錄集。 2.)是繼承JDBCTemplate或NamedParamterJDBCTemplate並重寫'getPreparedStatementCreator'方法以提供您自己的PreparedStatement,它將記錄流。 – Bunti

0

您的問題一次從數據庫加載應用程序服務器上的所有數據,嘗試使用limitoffset參數(必需order by)運行查詢,將加載的記錄推送到客戶端,並使用不同的offset加載下一部分數據。它可以幫助您減少內存佔用,並且不需要始終保持與數據庫連接的連接。當然,數據庫會加載多一點,但也許整個情況會更好。在應用服務器和數據庫上嘗試使用不同的limit值(例如5K-50K)並監視CPU使用情況。

如果你可以讓許多開放連接數據庫@Bunti答案是非常好的。

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

相關問題