1. 程式人生 > >Spring/Hibernate 應用效能優化的7種方法

Spring/Hibernate 應用效能優化的7種方法

【編者按】對於大多數典型的 Spring/Hibernate 企業應用而言,其效能表現幾乎完全依賴於持久層的效能。此篇文章中將介紹如何確認應用是否受資料庫約束,同時介紹七種常用的提高應用效能的速成法,由OneAPM 工程師翻譯。

以下為譯文

如何確認應用是否受限於資料庫

確認應用是否受限於資料庫的第一步,是在開發環境中進行測試,並使用 VisualVM 進行監控。VisualVM 是一款包含在 JDK 中的 Java 分析器,在命令列輸入 jvisualvm 即可呼叫。啟用 Visual VM 之後,嘗試以下步驟:

  • 雙擊你正在執行的應用
  • 選擇 Sampler
  • 點選 Settings 複選框
  • 選擇Profile only packages,然後輸入下列包:
your.application.packages.*
org.hibernate.*
org.springframework.*
your.database.driver.package, 比如 oracle.*
點選 Sample CPU

如果應用效能受限於資料庫,其 CPU 分析結果看起來會像下圖


我們看到,客戶端 Java 程序花在等待資料庫從網路中返回結果的時間佔56%。

看到資料庫查詢是導致應用執行緩慢的原因,其實是好兆頭。Hibernate 反射呼叫佔比32.7%是正常情況,無法進一步優化。

效能調優第一步:定義基準執行

效能調優的第一步是為程式定義基準執行,我們要定義一組能有效執行的輸入資料,讓程式基準執行與生產環境下的執行差不多。

主要的區別在於基準執行的耗時要小很多。作為參考,5到10分鐘的執行時間比較不錯。

什麼是好的基準?

好的基準應該具備以下特徵:

  • 功能正確
  • 輸入資料的種類與生產環境下相似
  • 在短時間內執行完畢
  • 基準執行的優化方案可以外推至完整執行

定義好的基準是成功解決問題的一半。

什麼是不好的基準

例如,通過批量執行處理通訊系統的電話資料記錄,選取10000條記錄就是錯誤的做法。

原因是:前10000條記錄可能多為語音電話,而未知的效能問題可能發生在簡訊流量的處理過程中。一開始如果基準不夠好,就會導致錯誤的結論。

收集 SQL 日誌與查詢時間

SQL 查詢的執行語句與其執行時間可以通過 log4jdbc等方式收集。詳細瞭解如何使用 log4jdbc 收集 SQL 查詢資訊,點選文章

使用 log4jdbc 優化 Spring/Hibernate 應用 SQL 日誌

查詢的執行時間是從 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 Git 都包含了 bash 命令列。

常用的速成法

下面介紹的速成法能找出 Spring/Hibernate 應用中常見的效能問題,以及對應的解決方案。

速成法1——減少生成主鍵的代價

在插入操作頻繁的程序中,主鍵的生成策略很重要。生成 id 的一種常見方法是使用資料庫序列,通常一張表一個 id,從而避免在不同表間進行插入時的衝突。

問題在於,如果要插入50條記錄,我們希望為了獲取這50個 id,可以避免50趟查詢資料庫的來回網路呼叫,讓 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%的效能提升,而且幾乎沒有改動程式碼。

速成法2——使用 JDBC 批處理 inserts/updates

對於批處理程式,JDBC 驅動程式提供了旨在減少網路來回傳輸的優化方法:”JDBC batch inserts/updates“。使用該方法後,插入或更新會先在驅動層排隊,然後再傳送到資料庫。

當達到閾值後,所有排隊的語句都會一次性傳給資料庫。這可以避免驅動程式逐一傳送語句,導致網路來回傳送的負擔。

經過以下配置,就能啟用批處理 inserts/updates:

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

僅設定 JDBC 批處理大小並不夠。因為 JDBC 驅動程式只會在收到對同一張表 insert/updates 時批處理這些語句。

如果收到對一張新表的插入語句,JDBC 驅動程式會先清除對前一張表的批處理語句,然後開始分批處理針對新表的 SQL 語句。

Spring Batch 內建了相似的功能。該優化能在插入操作頻繁的應用中帶來30%到40%的效能提升,而不用改動任何程式碼行。

速成法3——定期清理 Hibernate 會話

在向資料庫新增或修改資料時,Hibernate 會在會話中保留一版已經存在的實體,以防在會話關閉之前這些實體再度被修改。

但是,多數情況下,一旦對應的插入操作已經在資料庫中完成,我們就可以安心地丟棄那些實體。這會釋放 Java 客戶端程序中的記憶體,避免過久的 Hibernate 會話導致的效能問題。

這種長久的會話應該儘量避免。但如果出於某種原因不得不使用它們,以下是控制記憶體消耗的方法:

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

flush 會觸使新實體中的插入語句傳送至資料庫。clear 則會釋放會話中的新實體。

速成法4——減少 Hibernate dirty-checking(髒資料檢查) 的代價

Hibernate 內部使用了一種機制用於追蹤被修改的實體,名為 dirty-checking。該機制並不基於實體類中的 equals 和 hashcode 方法。

Hibernate 儘可能將 dirty-checking 的效能成本保持在最低值,只在需要時使用 dirty-check。但是該機制也有成本,在列數很多的表中該成本尤其可觀。

在進行任何優化之前,最重要的是使用 VisualVM 測量 dirty-checking 的成本。

如何避免 dirty-checking ?

dirty-checking 可以通過以下方式禁用:

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

禁用 dirty-checking 的另一種方式是使用 Hibernate 無狀態會話,預知詳情請檢視文件。

速成法5——搜尋”壞“查詢計劃

檢查最慢查詢列表,看看有沒有好的查詢計劃。最常見的”壞“查詢計劃包括:

全表搜尋:通常缺少一個索引或表統計過期時進行全表搜尋。

全笛卡爾連線:意思是計算多張表的全笛卡爾乘積。檢查一下缺少的連線條件,或拆分為幾個步驟以簡化查詢。

速成法6——檢查錯誤的提交間隔

如果你使用批處理程式,提交間隔會對效能造成十倍甚至百倍的影響。

請確保提交間隔是符合預期的(對於 Spring 批任務,通常是100到1000之間)。經常,該引數的配置不正確。

速成法7—— 使用二級查詢快取

如果一些資料可以快取,則可以檢視本文了解如何設定 Hibernate 快取:Hibernate 二級/查詢快取的陷阱。

結論

解決應用效能問題的關鍵,在於通過收集一些指標發現當前的瓶頸。

沒有一些測量指標,往往無法在短時間內找到真正的問題根源。

此外,很多典型的資料庫驅動應用的效能陷阱,如果一開始就使用了 Spring Batch,就能夠避免。