Spring / Hibernate 應用性能調優

來源:ImportNew - 陳曉舜spring

 

對大部分典型的Spring/Hibernate企業應用來講,應用的性能大部分由持久層的性能決定。sql

 

這篇文章會重溫一下怎麼去確認咱們的應用是不是」數據庫依賴(data-bound)」(譯者注:即很是依賴數據庫,大量時間花在數據庫操做上),而後會大概過一下7個經常使用的提高應用性能的速效方案。數據庫

 

怎麼肯定應用是不是「數據庫依賴」緩存

 

確認一個應用是是不是數據庫依賴,首先經過在一些開發環境中作基本的運行,可使用VisualVM來進行監控。VisualVM是一個和JDK一塊兒發佈的Java性能調優器,能夠經過命令行jvisualvm運行。安全

 

執行Visual VM後,嘗試下面的步驟:bash

 

  1. 雙擊你正在運行的應用網絡

  2. 選擇抽樣器(Sampler)session

  3. 點擊設置複選框oracle

  4. 選擇只調優包,而且限定以下的包類型:app

  • 你的應用程序包

  • org.hibernate.*

  • org.springframework.*

  • 你的數據庫jar包名,如oracle.*

  • 點擊抽樣(Sample) CPU

 

CPU抽樣一個典型的「數據庫依賴」應用將會獲得相似下面的結果:

 

 

 

咱們能夠看到Java客戶端進程花費了56%的時間在等待數據庫從網絡中返回結果。

 

這是一個很好的標誌,表示正是數據庫查詢形成了應用的緩慢。Hibernate反射調用佔了32.7%是正常,並且咱們對此也無能爲力。

 

性能調優第一步 —— 獲得基準運行值(baseline run)

 

性能調優的第一步是爲程序定義一個基準運行值。咱們須要一系列可使程序運行的有效輸入數據,它必須跟在生產環境運行相似。

 

最主要的區別是基準運行須要在更短的時間內運行完成,比較理想的指導值是執行時間爲5-10分鐘。

 

什麼是好的基準(baseline)?

 

一個好的基準須要有下面的特性:

 

  • 保證功能正確

  • 輸入數據在可變性上和生產環境相似

  • 在短期內能夠完成

  • 在基準運行中作的優化能夠直接影響到完整運行

 

取一個好的基準能夠解決一大半的問題。

 

什麼是很差的基準

 

例如,在一個批處理運行的執行電話數據記錄的電信系統中,取得前10000條記錄會是一個錯誤的作法。

 

緣由是:前10000條有可能大部分是語音電話,但未知的性能問題倒是在處理短信通道(SMS traffic)。在一個大批量執行的過程當中獲取前面的一些記錄不是一個好的基準,有可能會獲得錯誤的結論。

 

收集SQL日誌和查詢時間

 

SQL查詢和執行時間可使用如log4jdbc來進行收集。能夠看這篇博客關於如何使用log4jdbc來收集SQL查詢 —— 經過log4jdbc來改進Spring/Hibernate的SQL日誌(http://blog.jhades.org/logging-the-actualreal-sql-queries-of-a-springhibernate-application/).

 

查詢執行時間是在Java客戶端進行計算的,它包含了到數據庫的網絡往返請求耗時。SQL查詢日誌看起來就像這樣:

 

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

 

Prepared statements本身也是很好的信息源——它容許識別常常執行的查詢類型。根據這篇博客,能夠很簡單地記錄——Hibernate在哪裏,爲何作這個SQL查詢(http://blog.jhades.org/how-to-find-out-why-hibernate-is-doing-a-certain-sql-query/)。

 

SQL日誌能夠獲得什麼數據

 

SQL日誌能夠回答這些問題:

 

  • 最慢的查詢是什麼?

  • 最頻繁的查詢是什麼?

  • 生成主鍵花了多少時間?

  • 是否有數據能夠經過緩存受益?

 

怎麼轉換SQL日誌

 

也許對於大日誌文件最可行的方案就是使用命令行工具。這個方法的優勢是比較靈活。

 

只須要耗費點時間寫一小段腳本或命令,咱們能夠抽取大部分任何須要的數據。任何命令行均可以按你喜歡的方式去使用。

 

若是你使用Unix命令行,bash會是一個很好的選擇。Bash也能夠在Windows工做站中使用,使用例如Cygwin或Git這些包含bash命令行的工具。

 

經常使用的速效方案

 

下面的速效方案能夠識別Spring/Hibnerate應用中的常見性能問題和對應的解決方案。

 

速效方案1 —— 減小主鍵提早生成

 

在一些插入密集(intert-intensive)的處理中,主鍵生成策略的選擇有很大的影響。一個常見的生成ID的方法是使用數據庫的序列(sequences),一般每一個表一個,以免插入不一樣表時的衝突。

 

問題在於,若是插入50條記錄,咱們但願能夠避免50次經過數據庫獲取50個ID的網絡往返,而不使Java進程在大部分時間內等待。

 

Hibernate一般是怎麼處理這個的?

 

Hibernate提供了新優化的ID生成器能夠避免這個問題。對於sequences,會默認使用一個HiLo id生成器。HiLo序列生成器的工做過程以下:

 

  • 調用一次sequence返回1000(最大值)

  • 以下計算50個ID:

  • 1000 * 50 + 0 = 50000

  • 1000 * 50 + 1 = 50001

  • 1000 * 50 + 49 = 50049, 達到小值 (50)

  • 調用sequence獲取更大的值1001 …依此類推…

 

因此從第一次sequence調用時,就已經生成了50個key了,減小了大量的網絡往返耗時。

 

這些新優化的主鍵生成器在Hibernate4中是默認開啓的,在須要時,能夠經過設置hibernate.id.new_generator_mappings爲false進行關閉。

 

爲何主鍵生成仍然是個問題?

 

問題在於,若是你定義主鍵生成策略爲AUTO,優化生成器仍然是關閉的,你的應用仍然仍是會進行很大數量的sequence調用。

 

爲了保證新的優化生成器被啓用,確保使用SEQUENCE策略而不是AUTO:

 

@Id

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator")

private Long id;

 

有了這個小改變,’插入密集’的應用會有10%-20%的提高,而並不須要作其餘的代碼修改。

 

速效方案2 —— 使用JDBC批量插入/修改

 

對於批量的程序,JDBC驅動一般會提供稱之爲’JDBC批量插入/修改’的優化方案用於減小網絡往返消耗。在使用它們時,插入/修改在發送到數據庫前會在驅動層排隊(譯者注:達到必定的數量後會一次性發送多條SQL進行執行)。

 

當指定的閥值達到後,隊列中的批量語句將會被一次性發送到數據庫。這防止了驅動一個接一個的發送請求,浪費多個網絡請求。

 

下面是用於啓用批量插入/更新的entity manager factory的配置:

 

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

<prop key="hibernate.order_inserts">true</prop>

<prop key="hibernate.order_updates">true</prop>

 

只是設置JDBC batch size不會生效。這是由於JDBC驅動只有在具體某個相同的表接收到插入/更新時纔會把插入當成批量處理。

 

若是接收到對一個新表的插入命令,JDBC驅動會在執行新表的批量語句前先送出上一個表的批量的語句。

 

使用Spring Batch時也有隱晦地使用到一個相似的功能。這個優化能夠很簡單地爲你的「插入密集」應用節省30%到40%的時間,而不須要修改一行代碼。

 

速效方案3 —— 按期刷新和清空Hibernate session

 

當添加/修改數據庫數據時,爲了防止它們在session關閉後被從新修改,Hibnerate會在session中保持已經持久化的實體的版本。

 

但不少時候,在插入數據庫完成後,咱們能夠安全地丟棄實體。這能夠在Java客戶端釋放內存,防止因爲長時間運行Hibernate session形成的性能問題。

 

這種長時間運行的session應該被儘可能避免,但若是因爲某些緣由確實須要使用,下面的代碼展現了怎麼繼續保存內存引用:

 

entityManager.flush();

entityManager.clear();

 

這個flush會觸發發送操做,新實體的插入操做會被馬上發送到數據庫。clear會從session中釋放新實體。

 

速效方案4 —— 減小Hibernate提早的dirty-check

 

Hibernate使用稱之爲dirty-checking的內部的機制來跟蹤修改的實體。這個機制並不基於實體的equals和hashcode方法。

 

Hibnerate竭盡所能使dirty-checking的性能損耗降到最小,只有在須要的時候才進行dirty-check,但這個機制依然是會有損耗的。在有大量字段的表時尤爲須要注意。

 

在執行任何優化前,最重要的就是使用VisualVM計算一下dirty-check的損耗。

 

怎麼避免dirty-check

 

在Spring中,咱們所知的業務方法是隻讀的,dirty-check能夠經過下面的方法進行關閉:

 

@Transactional(readOnly=true)

public void someBusinessMethod() {

....

}

 

另一個可選的避免dirty-check的方法就是使用Hibnerate無狀態Session(Stateless Session),在文檔中有詳細描述。

 

速效方案5 —— 查找「壞」查詢方案

 

檢查一下在最慢查詢列表中的查詢,看看它們是否有好的查詢方案。最多見的「壞」查詢方案是:

 

  • 全表查詢(Full table scans):它發生在當表因爲缺失索引或過時的表數據而被全量掃描。

     

  • 徹底笛卡爾鏈接(Full cartesian joins):這意味着多個表計算徹底笛卡爾積。檢查一下是否缺乏鏈接條件,或是否能夠經過分割語句來避免。

 

速效方案6 —— 檢查錯誤的提交間隔

 

若是你正在作批量處理,提交的間隔在性能結果中能夠形成巨大的差異,能夠達到10-100倍。

 

確認一個提交的間隔是所指望的(Spring Batch通常是100-1000)。它一般是由於這個參數沒有正確配置。

 

速效方案7 —— 使用二級和查詢緩存

 

若是發現某些數據很適合緩存,那麼看一下這篇文章怎麼去配置Hibernate緩存:Hibernate二級/查詢緩存的陷阱(http://blog.jhades.org/setup-and-gotchas-of-the-hibernate-second-level-and-query-caches/)。

 

結論

 

要解決應用的性能問題,要作的最重要的就是收集一些能夠找到當前瓶頸所在的數據。沒有一些數據,基本上不可能在有效的時間內猜到問題在哪裏。而且,雖然不是全部,但不少的典型的「數據庫依賴」的應用性能陷阱均可以經過使用Spring Batch框架在第一時間避免。

相關文章
相關標籤/搜索