轉自http://blog.csdn.net/liweisnake/article/details/51683090
性能優化的常見概念
吞吐量(TPS, QPS):簡單來說就是每秒鐘完成的事務數或者查詢數。通常吞吐量大表明系統單位時間能處理的請求數越多,所以通常希望TPS越高越好
響應時間:即從請求發出去到收到系統返回的時間。響應時間一般不取平均值,而是要去掉不穩定的值之后再取均值,比如常用的90%響應時間,指的就是去掉了10%不穩定的響應時間之后,剩下90%的穩定的響應時間的均值。從聚類的觀點看,其實就是去掉離群點。
錯誤率:即錯誤請求數與總請求數之比。隨著壓力增加,有可能出現處理請求處理不過來的情況,這時錯誤數會不斷增加。
三者有極大的關聯,任何孤立的數據都不能說明問題。典型的關系是,吞吐量增加時,響應延遲有可能增加,錯誤率也有可能增加。因此,單拿出一個10w的TPS并不能說明問題。
性能調優的思路
一般情況,調優需要有個前提條件,即無論是用線上的真實流水還是線下的壓力測試讓問題擴大化,明顯化。
根據這些比較明顯的現象去初判問題,收集證據去驗證初判結果成立,然后分析現象產生的原因,并嘗試解決問題。
1.性能摸底測試
對于新上的系統或者是有過較大代碼改動的系統來說,做一次摸底測試還是很有必要的。一般來說,期望摸底的測試是一次對單機的壓力測試。壓力測試可以幫你大概搞清楚系統的極限TPS是多少,在壓力上來時有沒有暴露一些錯誤或者問題,系統大致的資源占用情況是什么,系統可能的性能瓶頸在哪。
如下是一次摸底測試的配置和結果。這是用12000并發用戶對10臺機器壓測的結果,可以看出,TPS到7w多,平均響應時間為82ms,錯誤率在2.5%。
從圖中還可以得到哪些信息?首先,TPS在后期迅速下落,實際上已經支撐不了如此大的并發量,即進入崩潰區,這里有幾個可能,一是系統根本承受不了如此大的并發量,二是系統中間有問題導致TPS下跌。其次,隨著時間增長,錯誤率顯著增加,說明系統已經處理不了如此多的請求。結合前面兩點以及相對平穩的平均響應時間,大致可以推斷系統沒法承受如此大的并發。另外,由于是10臺機器,單臺的TPS大概在7000多,今后的調優可以以此為依據。
對于應用的特點,也要在這時候分析出來,即應用可能占用的資源。比如是CPU密集型應用還是IO密集型應用(還可以細分為是磁盤密集還是網絡 )
2.定義性能優化的目標
經常聽到人說,做個性能優化,吞吐量越高越好;或者做個性能測試,目標TPS是50000??蓪嶋H拿到這個信息,能夠做性能測試嗎?這個目標足夠清晰嗎?
事實上,在我看來,未定義清晰的目標去做性能測試都是耍流氓。
性能優化的目標一般是吞吐量達到多少,90%響應時間小于多少,錯誤率小于多少。同時還需要關注其他的性能指標,cpu使用情況,內存使用情況,磁盤使用情況,帶寬使用情況等。對于摸底測試已經發現問題的,可以針對該問題專門優化,比如負載較高,cpu消耗過大,則目標可能是TPS,響應時間以及錯誤率不變的情況下降低CPU負載?;蛘邇却嬖鲩L過快,gc較為頻繁,則目標可能是找出可能的內存泄露,或者進行相關的jvm內存調優。總之,目標可以比較靈活調整,但一定要明確。
3.分析
分析的過程較為靈活,基本上是一千個系統有一千種表現。這里很難一一說明。僅談談一些常見的方法,工具以及思路。
針對CPU:
針對cpu的監控,其實linux已經提供了兩個比較好用的工具,一個是top,一個是vmstat。關于這兩個命令就不細說了,參考這里top(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/top.html),vmstat(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/vmstat.html)
關于cpu主要關注4個值:us(user), sy(system), wa(wait), id(idle)。理論上他們加起來應該等于100%。而前三個每一個值過高都有可能表示存在某些問題。
us過高:
a. 代碼問題。比如一個耗時的循環不加sleep,或者在一些cpu密集計算(如xml解析,加解密,加解壓,數據計算)時沒處理好
b. gc頻繁。一個比較容易遺漏的問題就是gc頻繁時us容易過高,因為垃圾回收屬于大量計算的過程。gc頻繁帶來的cpu過高常伴有內存的大量波動,通過內存來判斷并解決該問題更好。
小技巧:如何定位us過高的線程并查看它的狀態。
a. top命令找到消耗us過高的進程pid
b. top -Hp pid找到對應的線程tid
c. printf %x tid轉為16進制tid16
d. jstack pid | grep -C 20 tid16 即可查到該線程堆棧
sy過高:
a. 上下文切換次數過多。通常是系統內線程數量較多,并且線程經常在切換,由于系統搶占相對切換時間和次數比較合理,所以sy過高通常都是主動讓出cpu的情況,比如sleep或者lock wait, io wait。
wa過高:
a. 等待io的cpu占比較多。注意與上面情況的區別,io wait引起的sy過高指的是io不停的wait然后喚醒,因為數量較大,導致上下文切換較多,強調的是動態的過程;而io wait引起的wa過高指的是io wait的線程占比較多,cpu切換到這個線程是io wait,到那個線程也是io wait,于是總cpu就是wait占比較高。
id過高:
a. 很多人認為id高是好的,其實在性能測試中id高說明資源未完全利用,或者壓測不到位,并不是好事。
針對內存:
關于java應用的內存,通常只需要關注jvm內存,但有些特殊情況也需要關注物理內存。關于jvm內存,常見的工具有jstat(http://blog.csdn.net/fenglibing/article/details/6411951), jmap(http://www.cnblogs.com/ggjucheng/archive/2013/04/16/3024986.html), pidstat(https://linux.cn/article-4257-1.html), vmstat, top
jvm內存:
**異常gc **:
a. 通常gc發生意味著總歸是有一塊區域空間不足而觸發gc。而許多導致異常gc的情況通常是持有了不必要的引用而沒有即時的釋放,比如像cache這樣的地方就容易處理不好導致內存泄露引發異常gc。
b. 有可能是程序的行為是正常的,但是由于沒有配置對合適的gc參數導致異常gc,這種情況通常需要調優gc參數或者堆代大小參數。
c. Full gc 發生的情況:
永久代滿
年老代滿
minor gc晉升到舊生代的平均大小大于舊生代剩余大小
CMS gc中promotion fail或concurrent mode fail
OOM:
a. OOM經常伴隨著異常gc,之所以單獨拿出來講,是因為它的危害更大一些,異常gc頂多是收集速度過快或者回收不了內存,但是起碼有個緩沖時間,但是出了OOM問題就大了。至于各種類型的OOM如何區分,如何發生,請參考這里(http://www.lxweimin.com/p/2fdee831ed03),算是總結得比較全面的。對于常見的OOM,基本上可以一下子指出問題所在。
b. heap區,對象創建過多或持有太多無效引用(泄露)或者堆內存分配不足。使用jmap找到內存中對象的分布,使用ps找到相應進程及初始內存配置。
c. stack區, 不正確的遞歸調用。
d. perm區,初始加載包過多,分配內存不足。
e. 堆外內存區,分配ByteBuffer未釋放導致。
針對IO:
IO分為網絡IO和文件IO,針對網絡IO比較有用的工具有sar(https://linuxstory.org/generate-cpu-memory-io-report-sar-command/),netstat(https://linux.cn/article-2434-1.html),netstat是一個非常牛逼的命令,可以助于排查很多問題, 針對文件io的工具有pidstat,iostat(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/iostat.html)
文件IO:
a. 從技術上來說,對于大文件IO可以采取的措施是異步批處理,采用異步方式用于削峰并累計buffer,采用批處理能夠讓磁盤尋道連續從而更加快速。
網絡IO:網絡IO的問題較為復雜,僅舉幾個常見的
a. 大量TIME_WAIT。根據TCP協議,主動發起關閉連接的那一方,關閉了自己這端的連接后再收到被動發起關閉的那一方的關閉請求后,會將狀態變為TIME_WAIT,并等待2MSL, 目的是等待自己的回執發送到對方。如果在服務器上發現大量TIME_WAIT,說明服務器主動斷開了連接,什么情況下服務器會主動斷開連接,很可能是客戶端忘了斷開連接,所以一個典型的案例就是jdbc連接忘記關閉,則數據庫服務器可能會出現大量的TIME_WAIT狀態。
b. 大量CLOSE_WAIT。CLOSE_WAIT狀態,在收到主動關閉連接的一方發出關閉連接之后,被動關閉的一方進入CLOSE_WAIT狀態,如果這時候被hang住了沒進行后續關閉,則會出現大量CLOSE_WAIT。啥情況會被hang住呢,舉幾個例子,比如剛剛的忘記關閉數據庫連接,在應用服務器這端,大量的瀏覽器請求進來,由于沒有連接池連接被hang住,這時候瀏覽器等待一定時間超時發送關閉連接請求,而應用服務器這邊由于servlet線程被hang住了,自然沒有辦法走第二個關閉回去。因此在應用服務器出現大量CLOSE_WAIT。另一個例子是httpClient的坑,在調用response.getEntity(); 前都不會做inputStream.close(),如果在調用response.getEntity()前就返回了,就狗帶了。(這個例子可以參考http://blog.csdn.net/shootyou/article/details/6615051)
4.****優化并重新測試驗證
性能調優思路 http://www.voidcn.com/blog/bigtree_3721/article/p-5786972.html
linux下性能監控命令 http://linuxtools-rst.readthedocs.io/zh_CN/latest/advance/index.html
關于JVM CPU資源占用過高的問題排查 http://my.oschina.net/shipley/blog/520062
java排查工具 http://my.oschina.net/feichexia/blog/196575
jvm參數調優 http://www.cnblogs.com/java-zhao/archive/2016/02/08/5185092.html
java linux系統調優工具 https://www.ibm.com/developerworks/cn/java/j-lo-performance-tuning-practice/
gc優化的一些思路 http://mm.fancymore.com/reading/gc%E4%BC%98%E5%8C%96%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%9D%E8%B7%AF.html
性能優化的思路和步驟 http://www.uml.org.cn/j2ee/201602013.asp
性能調優攻略 http://coolshell.cn/articles/7490.html
JVM性能調優入門 http://www.lxweimin.com/p/c6a04c88900a
JVM性能調優 http://blog.csdn.net/chen77716/article/details/5695893
Tomcat性能優化 https://yq.aliyun.com/articles/38861?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&2017323&utm_content=m_14698