性能問題定位套路

前面的話

我們在工作過程中,肯定會遇到性能調(diào)優(yōu)及內(nèi)存溢出的問題,本篇文章會通過幾個(gè)小例子來粗略的介紹性能定位的思路及工具的使用。

性能問題分類

我們經(jīng)常遇到的服務(wù)端的性能問題一般有如下幾種:

1、接口時(shí)延過高,TPS不達(dá)標(biāo)
2、內(nèi)存溢出

栗子說明

本文栗子為使用 springboot 快速開發(fā)了兩個(gè) http 接口,一個(gè)是列表排序栗子,模擬耗時(shí)操作,一個(gè)是往一個(gè)全局列表中不停的插入數(shù)據(jù)達(dá)到內(nèi)存溢出的效果。
關(guān)于列表排序,這里使用兩種排序方式,一種是簡單的冒泡排序,一種是 jdk 里列表的排序方式:加強(qiáng)型多路歸并排序,用兩個(gè)排序算法主要為了說明 JHM 的使用方式。

TPS 不達(dá)標(biāo)問題分析

對于此類問題,則一般是在性能測試階段就能發(fā)現(xiàn)。此時(shí)調(diào)優(yōu)一般在性能測試環(huán)境上進(jìn)行。
如何找出耗時(shí)操作呢,JDK 已經(jīng)給我們提供了一系列的工具來定位該問題了,這里我們使用Java VisualVM來診斷接口性能。
首先在啟動腳本里打開 JVM 的 JMX 端口,打開方式為-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.234.196.199
啟動之后,我們就可以通過Java VisualVM來監(jiān)控我們的 JVM 了。
如圖:

性能監(jiān)控1.png

打開抽樣器,進(jìn)行 CPU 抽樣,統(tǒng)計(jì)各個(gè)接口消耗 CPU 時(shí)間。
使用壓測工具,持續(xù)的壓測有性能問題的接口。這里使用 jmeter 進(jìn)行壓測。壓測一段時(shí)間后,打印 CPU 快照,如圖:


性能監(jiān)控2.png

這里發(fā)現(xiàn)我們在調(diào)用 getPerf 接口時(shí),進(jìn)一步調(diào)用了 process1 接口,這個(gè)接口里有 bubbleSort 方法和 jdk 自帶的 sort 兩個(gè)調(diào)用。這兩個(gè)都是對列表排序,發(fā)現(xiàn)大部分時(shí)間都耗在冒泡排序上。這里 bubbleSort 就是需要優(yōu)化的地方。排序算法有很多,不同的數(shù)據(jù)量,不同的排序方法耗時(shí)也不一樣。這里需要用 JMH 來評估算法的耗時(shí)。
JMH 相關(guān)介紹可以參考JMH,這里有相關(guān)的例子可以參考

一般對于耗時(shí)操作的優(yōu)化,可以有如下方式:
1、優(yōu)化自身算法,降低算法的時(shí)間復(fù)雜度
2、同步操作異步化。
對于異步化操作,又有如下方式:

1、異步線程
2、線程池,線程復(fù)用(線程池的大小如何確定,CPU 密集型和 IO 密集型)
3、發(fā)布訂閱(消息隊(duì)列或者 spring 的 event 機(jī)制)

3、使用緩存機(jī)制【多級緩存,問題:緩存一致性,緩存防并發(fā),防雪崩----一個(gè)大專題】
4、業(yè)務(wù)流程上進(jìn)行優(yōu)化,提供專門的接口,只做當(dāng)前業(yè)務(wù),不考慮復(fù)用性。
5、如果是數(shù)據(jù)庫查詢慢,則需要優(yōu)化數(shù)據(jù)庫【這又是一個(gè)大專題】。sql 優(yōu)化??? 表優(yōu)化,如果有聯(lián)表查詢,則可以考慮不滿足 3 范式,拉平表結(jié)構(gòu)。

如果無法在測試環(huán)境上復(fù)現(xiàn),則可以試用 arthas 工具,attach 到相關(guān)進(jìn)程,通過 arthas 命令大致查看每個(gè)請求的耗時(shí)。關(guān)于 arthas 的用法,可以參考arthas

內(nèi)存溢出問題分析

為什么內(nèi)存溢出會出現(xiàn)接口時(shí)延過高呢?
我們服務(wù)端一般是 JAVA 語言開發(fā),如果 JVM 虛擬機(jī)內(nèi)存不足時(shí),會觸發(fā) FullGC,F(xiàn)ullGC 會吃大量的 CPU 時(shí)間。如果我們的內(nèi)存一直不足,頻繁的 GC,則會 STW,CPU 居高不下,留給業(yè)務(wù)的 CPU 時(shí)間就降低,導(dǎo)致業(yè)務(wù)接口時(shí)延上升。
內(nèi)存溢出的例子代碼如下

 public void process2() {
        String name = "The Spring Framework provides a comprehensive programming and configuration model for" +
                "modern Java-based enterprise applications - on any kind of deployment platform" +
                "A key element of Spring is infrastructural support at the application level: Spring focuses on the" +
                "Complete set of java.time based setters on HttpHeaders, CacheControl, CorsConfiguration.\n" +
                "@RequestMapping has enhanced produces condition support such that if a media type is declared with a specific parameter, and the requested media types (e.g. from \"Accept\" header) also has that parameter, the parameter values must match. This can be used for example to differentiate methods producing ATOM feeds \"application/atom+xml;type=feed\" vs ATOM entries \"application/atom+xml;type=entry\".\n" +
                "CORS revision that adds Vary header for non CORS requests on CORS enabled endpoints and avoid considering same-origin requests with an Origin header as a CORS request.\n" +
                "Upgrade to Jackson 2.10\n" +
                "Spring Web MVC\n" +
                "New \"WebMvc.fn\" programming model, analogous to the existing \"WebFlux.fn\":\n" +
                "A functional alternative to annotated controllers built on the Servlet API.\n" +
                "WebMvc.fn Kotlin DSL.\n" +
                "Request mapping performance optimizations through caching of the lookup path per HandlerMapping, and pre-computing frequently used data in RequestCondition implementations.\n" +
                "Improved, compact logging of request mappings on startup.\n" +
                "Spring WebFlux\n" +
                "Refinements to WebClient API to make the retrieve() method useful for most common cases, specifically adding the ability to retrieve status and headers and addition to the body. The exchange() method is only for genuinely advanced cases, and when using it, applications can now rely on ClientResponse#createException to simplify selective handling of exceptions.\n" +
                "Support for Kotlin Coroutines.\n" +
                "Server and client now use Reactor checkpoints to insert information about the request URL being processed,sce or the handler used, that is then inserted into exceptions and logged below the exception stacktrace.\n" +
                "Request mapping performance optimizations through pre-computing frequently used data in RequestCondition implementations.\n" +
                "Header management performance optimizations by wrapping rather than copying server headers, and caching parsed representations of media types. Available from 5.1.1, see issue #21783 and commits under \"Issue Links\".\n" +
                "Improved, compact logging of request mappings on startup.\n" +
                "Add ServerWebExchangeContextFilter to expose the Reactor Context as an exchange attribute.\n" +
                "Add FreeMarker macros support.\n" +
                "MultipartBodyBuilder improvements to allow Publisher and Part as input along with option to specify the filename to use for a part.";
        list.add(name + System.currentTimeMillis());

    }

這里往一個(gè)全局的 list 中添加一個(gè)字符串,每次請求時(shí),添加一個(gè)字符串。
-Xms200m -Xmx200m這里把 jvm 堆內(nèi)存大小設(shè)置為 200m。
對于內(nèi)存溢出,則需要 gc log 和內(nèi)存快照。gc log 可以在https://gceasy.io上面分析,可以看到相關(guān)的fullgc和yong gc 的情況。gc 分析如圖:

gc分析.png

該圖表明發(fā)生 GC 之后,對大小并沒有明顯的減少,可能是堆內(nèi)存不太夠用。圖左邊的每個(gè)按鈕對應(yīng)一個(gè)分析。

定位出內(nèi)存不足后,就要看內(nèi)存中哪些對象回收不掉,這時(shí)需要使用到 jmap 命令,dump 出內(nèi)存快照。命令如下:
jmap -dump:format=b,file=heapdump.hprof pid?,
獲取到內(nèi)存快照可以使用 mat 進(jìn)行分析。
使用 mat 打開快照文件,如下:

mat_preview.png

這里看到最大一塊內(nèi)存是 81.7M,點(diǎn)擊餅圖進(jìn)入如下頁面:


list.png

上圖可以看到在類 businessServiceImpl 中有個(gè) list,該 list 共有 16081 個(gè)元素,每個(gè)元素大小 5296 個(gè)字節(jié)。共有 82M。
點(diǎn)擊 value,可以查看 list 中具體的值。如圖:


list_value_detail.png

發(fā)現(xiàn)正是我們代碼里插入的字符串。

CPU 高

使用 top 命令,查看哪個(gè)進(jìn)程 CPU 高,通過 top -p <PID> -H 查看哪個(gè)線程消耗 CPU。使用 jstack 命令打印出 java 進(jìn)程的線程堆棧,通過線程號找到相應(yīng)的 java 線程,結(jié)合 java 代碼,一般可以找出系統(tǒng)的耗 CPU 代碼。
相關(guān)操作可以參考如下文章:
誰偷走了你的服務(wù)器性能

寫在最后

這里只是通過一些栗子說明了性能工具的使用方法,只是一個(gè)引子,隨后會進(jìn)一步介紹如何進(jìn)行性能的優(yōu)化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 第二部分 自動內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,193評論 0 2
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,439評論 1 0
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 15,651評論 3 83
  • Java類別問題 1. String與StringBuilder、StringBuffer的區(qū)別 如果要操作少量...
    梁小中閱讀 368評論 0 4
  • 最近可以說是很喪了,無奈牙齒過敏一月未好只得去看,誰知五分鐘完事被愉悅地告知牙齒很健康也很干凈堅(jiān)持使用抗過敏牙膏,...
    陶一_閱讀 336評論 0 0