Spring/Hibernate應用性能調優

jopen 10年前發布 | 18K 次閱讀 Spring Hibernate JEE框架

對于大多數典型的Spring/Hibernate 企業應用來說,應用程序的性能幾乎完全取決于它的持久層的性能。

這篇文章將會對如何確認在“數據庫約束”的應用前,使用7種“快速見效”的技巧來幫助我們提升應用性能。

如何確認一個應用受到“數據庫約束”

為了驗證一個應用程序是否受到“數據庫約束”,首先在一些開發環境中做一些普遍的行為,即使用VisualVM來監控。 VisualVM是一個搭載JDK的Java解析器,它通過調用jvisualvm來進行命令行登陸。

登陸Visual VM后按照這樣做:

  • 運行你的應用程序
  • 選擇 Sampler
  • 點擊Settings復選框
  • 選擇 Profile only packages,同時引入下面的包:
    • your.application.packages.*
    • org.hibernate.*
    • org.springframework.*
    • your.database.driver.package, for example oracle.*
    • Click Sample CPU

一個典型“數據庫約束”應用的CPU性能分析應該像這樣:

ca65b5b40bdae4f1d03f0aae8fa467c3.jpeg

 

我們可以看到 Java進程的客戶端花費了56%的時間用來等待數據庫通過網絡返回結果。

這是個好的標志,它顯示了是什么讓數據庫查詢應用變慢的。32.7%的Hibernate反射調用可以正常運行所以沒什么可以改進的。

第一步優化:獲得運行基線

做優化的第一步是定義一個基線運行的程序。我們需要確定一組有效的輸入數據使程序通過一個類似于生產輸出的典型執行類。

主要的區別在于,基線運行應該在更短的時間內運行,作為一個指導方針的執行時間,5到10分鐘是一個理想的時間。

如何得到一個好的基線?

一個好的基線應該有以下特點:

  • 功能正確
  • 在類型方面輸入數據類似于輸出數據
  • 能在很短的時間內完成
  • 基線運行的優化可以進行推廣

良好的基線能事半功倍。

怎樣會得到一個糟糕的基線?

例如,在批處理調用一個通信系統的數據記錄中,獲取前 10 000 條記錄是錯誤的方法。

原因是這前10 000個數據可能是語音通話,而這些未知的錯誤可能是由于以短信的方式來處理導致的。大量采用這些記錄會導致產生一個錯誤的基線,由此錯誤的結論就產生了。

手機SQL日志和查詢計時

SQL查詢的執行時間可以收集用于log4jdbc。看這篇博客是如何使用log4jdbc收集SQL查詢 – Spring/Hibernate improved SQL logging with log4jdbc

查詢執行時間衡量的是Java客戶端,它包括往返到數據庫的網絡。SQL查詢日志是像這樣的:

16 avr. 2014 11:13:48 | SQL_QUERY /* insert your.package.YourEntity */ insert into YOUR_TABLE (...) values (...) {executed in 13 msec}

好的語句本身也是一個不錯的信息來源 –他們允許輕松地識別頻繁的查詢類型。他們記錄在以下這篇博客日志 – 為什么 Hibernate 做這樣的 SQL 查詢?

SQL日志可以找出哪些指標

SQL 日志可以回答下列問題:

  • 執行最慢的查詢是什么?
  • 最常見的查詢是什么?
  • 生成主鍵的時間量是多少?
  • 有數據可以受益于緩存嗎?

如何解析SQL日志

可能對于大量日志唯一可行的選擇是使用命令行工具。這種方法的優點是非常靈活的。

寫一個腳本或命令后我們可以提取主要指標。只要你覺得合適任何命令行工具都是可以用的。

如果你是使用Unix命令行,bash可能是一個不錯的選擇。Bash也可以在Windows工作站上使用,例如使用 Cygwin,或者包括bash命令行的 Git

頻繁應用 Quick-Wins

Quick-wins 能識別Spring/Hibernate應用中的常見問題并找到相應的解決方案。

Quick-win 技巧 1:減少主鍵生成開銷

在‘insert-intensive’的進程中,主鍵生成策略的選擇非常重要。一種常見的生成id的方法是’s用數據庫序列,通常每個表進行插入數據的時候避免爭用一個同一資源。

問題是如果插入50條記錄,我們想避免為了獲得50條記錄的id而造成的網絡往返對數據庫的消耗,這會導致大部分時間保持Java進程掛起。

Hibernate通常是如何處理的呢?

Hibernate提供了新的優化后的ID生成器來避免這個問題。這就是序列,一個 HiLo id 生成器是在默認情況下使用的。這是HiLo序列發生器如何工作的:

  • 一旦調用一個序列和1000個(高值的)
  • 像這樣來計算 50 個id的序列:
    • 1000 * 50 + 0 = 50000
    • 1000 * 50 + 1 = 50001
    • 1000 * 50 + 49 = 50049, 最低值 (50)
    • 調用更高的序列值 1001 … 等等 …

從一個序列的調用中可以看出,生產50個鍵可以減少很多網絡傳輸所造成的開銷。

這些新的主鍵優化的產生默認是基于Hibernate 4的,同時也可以在必要時將hibernate.id.new_generator_mappings設置為false來關掉它。

為什么生成主鍵依舊是個問題?

問題就是,如果你宣布主鍵生成策略是AUTO,優化后仍然是關閉的狀態, 這樣的話您的應用程序最終會調用大量的序列。

為了確保新的優化生成器處于運行狀態,保證使用SEQUENCE策略而不是AUTO

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator")
private Long id;

通過這個簡單的改變,有10%-20%的范圍可以改善,這可以在基本上沒有修改代碼的情況下在‘insert-intensive’應用程序中采用。

Quick-win 技巧 2:使用JDBC批處理插入/更新

對于批處理程序,JDBC驅動程序通常提供優化來減少網絡流量,這就是‘JDBC 批量插入/更新’。當使用這些的時候,驅動級別的插入/更新在發送到數據庫前被排入隊列。

當達到閾值后整個批處理隊列語句一次性發送到數據庫。這可以防止驅動逐條發送語句,這可以進行多網絡傳輸。

這是工廠配置的實體管理來激活插入/更新的批處理:

<prop key="hibernate.jdbc.batch_size">100</prop>
<prop key="hibernate.order_inserts">true</prop>
<prop key="hibernate.order_updates">true</prop>

只配置JDBC批處理的大小是不能正常工作的。這是因為JDBC驅動程序只有當接收到插入/更新完全相同的表時才會進行批處理插入操作。

如果新表收到一條插入語句,那么JDBC驅動會在開始對新表進行批處理操作前首先刷新前一張表的批處理語句。

如果使用Spring批處理的話,一個類似的功能是隱式地進行使用。這種優化可以很容易地完成30%40%的‘insert intensive’程序,而無需改動一行代碼。

Quick-win 技巧 3:定期刷新和清理Hibernate會話

當添加/修改數據庫的數據時,Hibernate保持了一個已經存在的實體版本的會話,以防在會話關閉之前進行修改。

但是很多時候,一定在數據庫中有匹配的插入時我們就可以安全地丟棄實體。這在Java客戶機進程中釋放了內存,防止長時間運行Hibernate會話所導致的性能問題。

像這樣長時間運行的會話應該盡可能被阻止,但是由于某種原因需要它們的話就應該包含內存是如何消耗的:

entityManager.flush();
entityManager.clear();

flush將觸發插入新實體從而發送到數據庫。clear 則從會話釋放新的實體。

Quick-win 技巧 4 :減少Hibernate過多的dirty-checking

Hibernate使用內部的一種機制來保持記錄修改的實體的方式就叫做 dirty-checking。這種機制不是基于實體的equals和hashcode方法的類。

Hibernate能讓dirty-checking的性能成本降至最低,dirty-check只會在需要的時候出現,但是這種機制也是有代價的,它有更多的表和列。

在應用做任何優化前,最重要的是測量使用VisualVM所耗費的dirty-checking的成本。

如何避免dirty-checking?

我們所知道的Spring事務方法是只讀的,dirty-checking可以像這樣來關閉:

@Transactional(readOnly=true)
public void someBusinessMethod() {
    ....
}

另一種避免dirty-checking的方式是使用Hibernate無狀態會話,這在documentation有詳細描述。

Quick-win 技巧 5:搜尋 “差的” 查詢計劃

在最慢的查詢列表里進行檢查來看他們是否有良好的查詢計劃。最常見的“差勁的”查詢計劃是:

  • 全表掃描:表完全地被掃描是因為經常缺少索引或者過時的表統計。
  • 笛卡爾連接:這意味著幾張表進行笛卡兒積后的結果正在進行計算。檢查正在丟失的連接條件,或者可以通過將一個步驟分為幾步來完成可以避免發生這個問題。

Quick-win 技巧 6:檢查錯誤的提交時間間隔

如果你是正在做批處理,那么提交間隔會在性能結果上產生很大的影響, 能達到10-100倍甚至更多。

確認提交間隔是所預期的(通常是Spring批處理作業的100-1000倍)。參數配置錯誤的情況時有發生。

Quick-win 技巧 7:使用二級查詢緩存

如果某些數據被確定為合格緩存,那么看看這篇博客如何設置Hibernate緩存的:Pitfalls of the Hibernate Second-Level / Query Caches

總結

為了解決應用程序性能問題,最重要的操作是收集一些指標來找到當前的瓶頸是什么。

不給定指標的話幾乎不可能在有意義的時間內發現是什么導致問題發生的。

同時,許多但并非所有 ‘數據庫-驅動’ 的表現缺陷可以避免在最先使用的Spring Batch框架的應用中發生。

原文鏈接: javacodegeeks 翻譯: ImportNew.com - Grey
譯文鏈接: http://www.importnew.com/12314.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!