Spring/Hibernate 應用性能調優
來源:ImportNew - 陳曉舜
對大部分典型的Spring/Hibernate企業應用來說,應用的性能大部分由持久層的性能決定。
這篇文章會重溫一下怎麼去確認我們的應用是否是」資料庫依賴(data-bound)」(譯者註:即非常依賴資料庫,大量時間花在資料庫操作上),然後會大概過一下7個常用的提升應用性能的速效方案。
怎麼確定應用是否是「資料庫依賴」
確認一個應用是是否是資料庫依賴,首先通過在一些開發環境中做基本的運行,可以使用VisualVM來進行監控。VisualVM是一個和JDK一起發布的Java性能調優器,可以通過命令行jvisualvm運行。
執行Visual VM後,嘗試下面的步驟:
雙擊你正在運行的應用
選擇抽樣器(Sampler)
點擊設置複選框
選擇只調優包,並且限定如下的包類型:
你的應用程序包
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 (...)
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:
所以從第一次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的配置:
true
true
只是設置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框架在第一時間避免。
最近熱文:
1、邀你參加11期 程序員專場相親活動
2、PHP高工/架構師,18-50K,深圳南山
3、JAVA/Web前端,深圳前海,15-30K
4、如何判斷是否到了該辭職的時候?
※iOS 編譯過程的原理和應用
※Java利用DES/3DES/AES這三種演算法分別實現對稱加密
※程序員如何面對自己三十歲後的下坡路?
※Java開發/PHP開發/Web前端,架構師,深圳南山
TAG:程序源 |
※Instagram的Python性能調優方法介紹
※利用Kubernetes和Helm進行高效的超參數調優
※前端學Serverless系列-性能調優
※3M 反光鞋面!低調優雅的 Air Jordan 1 「Rox Brown」 即將上架
※DDN收購Intel Lustre系統業務,詳解Lustre系統架構、配置和調優
※Spark調優的關鍵—RDD Cache緩存使用詳解
※Tomcat 調優測試
※keras參數調優
※低調優雅!近觀全新配色Nike Premier II足球鞋
※Tomcat 運維常用調優方式
※Spark 數據傾斜調優
※Hadoop虛擬化的性能對比和調優經驗
※Kafka參數調優實戰
※Python 環境下的自動化機器學習超參數調優
※Sql性能調優
※MySQL 性能調優的10個方法
※Lunasol秋季限量新品眼影,你想要的精緻低調優雅就是它!
※阿里P8架構師談:資料庫、JVM、緩存、SQL等性能調優方法和原則
※BERT模型的標準調優和花式調優
※上海旭輝M-CITY85㎡戶型樣板間,低調優雅的韻律