前面的話
我們?cè)诠ぷ鬟^程中吧黄,肯定會(huì)遇到性能調(diào)優(yōu)及內(nèi)存溢出的問題,本篇文章會(huì)通過幾個(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)于列表排序,這里使用兩種排序方式蔼囊,一種是簡(jiǎn)單的冒泡排序包颁,一種是 jdk 里列表的排序方式:加強(qiáng)型多路歸并排序,用兩個(gè)排序算法主要為了說明 JHM 的使用方式压真。
TPS 不達(dá)標(biāo)問題分析
對(duì)于此類問題,則一般是在性能測(cè)試階段就能發(fā)現(xiàn)蘑险。此時(shí)調(diào)優(yōu)一般在性能測(cè)試環(huán)境上進(jìn)行滴肿。
如何找出耗時(shí)操作呢,JDK 已經(jīng)給我們提供了一系列的工具來定位該問題了佃迄,這里我們使用Java VisualVM
來診斷接口性能泼差。
首先在啟動(dòng)腳本里打開 JVM 的 JMX 端口贵少,打開方式為-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.234.196.199
啟動(dòng)之后,我們就可以通過Java VisualVM
來監(jiān)控我們的 JVM 了堆缘。
如圖:
打開抽樣器滔灶,進(jìn)行 CPU 抽樣,統(tǒng)計(jì)各個(gè)接口消耗 CPU 時(shí)間吼肥。
使用壓測(cè)工具录平,持續(xù)的壓測(cè)有性能問題的接口。這里使用 jmeter 進(jìn)行壓測(cè)缀皱。壓測(cè)一段時(shí)間后斗这,打印 CPU 快照,如圖:
這里發(fā)現(xiàn)我們?cè)谡{(diào)用 getPerf 接口時(shí)啤斗,進(jìn)一步調(diào)用了 process1 接口表箭,這個(gè)接口里有 bubbleSort 方法和 jdk 自帶的 sort 兩個(gè)調(diào)用。這兩個(gè)都是對(duì)列表排序钮莲,發(fā)現(xiàn)大部分時(shí)間都耗在冒泡排序上免钻。這里 bubbleSort 就是需要優(yōu)化的地方。排序算法有很多崔拥,不同的數(shù)據(jù)量极舔,不同的排序方法耗時(shí)也不一樣。這里需要用 JMH 來評(píng)估算法的耗時(shí)握童。
JMH 相關(guān)介紹可以參考JMH,這里有相關(guān)的例子可以參考
一般對(duì)于耗時(shí)操作的優(yōu)化姆怪,可以有如下方式:
1、優(yōu)化自身算法澡绩,降低算法的時(shí)間復(fù)雜度
2稽揭、同步操作異步化。
對(duì)于異步化操作肥卡,又有如下方式:
1溪掀、異步線程
2、線程池步鉴,線程復(fù)用(線程池的大小如何確定揪胃,CPU 密集型和 IO 密集型)
3、發(fā)布訂閱(消息隊(duì)列或者 spring 的 event 機(jī)制)
3氛琢、使用緩存機(jī)制【多級(jí)緩存喊递,問題:緩存一致性,緩存防并發(fā)阳似,防雪崩----一個(gè)大專題】
4骚勘、業(yè)務(wù)流程上進(jìn)行優(yōu)化,提供專門的接口,只做當(dāng)前業(yè)務(wù)俏讹,不考慮復(fù)用性当宴。
5、如果是數(shù)據(jù)庫(kù)查詢慢泽疆,則需要優(yōu)化數(shù)據(jù)庫(kù)【這又是一個(gè)大專題】户矢。sql 優(yōu)化?殉疼?梯浪? 表優(yōu)化,如果有聯(lián)表查詢株依,則可以考慮不滿足 3 范式驱证,拉平表結(jié)構(gòu)。
如果無(wú)法在測(cè)試環(huán)境上復(fù)現(xiàn)恋腕,則可以試用 arthas 工具抹锄,attach 到相關(guān)進(jìn)程,通過 arthas 命令大致查看每個(gè)請(qǐng)求的耗時(shí)荠藤。關(guān)于 arthas 的用法伙单,可以參考arthas
內(nèi)存溢出問題分析
為什么內(nèi)存溢出會(huì)出現(xiàn)接口時(shí)延過高呢?
我們服務(wù)端一般是 JAVA 語(yǔ)言開發(fā)哈肖,如果 JVM 虛擬機(jī)內(nèi)存不足時(shí)吻育,會(huì)觸發(fā) FullGC,F(xiàn)ullGC 會(huì)吃大量的 CPU 時(shí)間淤井。如果我們的內(nèi)存一直不足布疼,頻繁的 GC,則會(huì) 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è)字符串贱案,每次請(qǐng)求時(shí),添加一個(gè)字符串止吐。
-Xms200m -Xmx200m
這里把 jvm 堆內(nèi)存大小設(shè)置為 200m宝踪。
對(duì)于內(nèi)存溢出,則需要 gc log 和內(nèi)存快照碍扔。gc log 可以在https://gceasy.io上面分析瘩燥,可以看到相關(guān)的fullgc和yong gc 的情況。gc 分析如圖:
該圖表明發(fā)生 GC 之后不同,對(duì)大小并沒有明顯的減少厉膀,可能是堆內(nèi)存不太夠用。圖左邊的每個(gè)按鈕對(duì)應(yīng)一個(gè)分析。
定位出內(nèi)存不足后站蝠,就要看內(nèi)存中哪些對(duì)象回收不掉,這時(shí)需要使用到 jmap 命令卓鹿,dump 出內(nèi)存快照菱魔。命令如下:
jmap -dump:format=b,file=heapdump.hprof pid
?,
獲取到內(nèi)存快照可以使用 mat 進(jìn)行分析。
使用 mat 打開快照文件吟孙,如下:
這里看到最大一塊內(nèi)存是 81.7M,點(diǎn)擊餅圖進(jìn)入如下頁(yè)面:
上圖可以看到在類 businessServiceImpl 中有個(gè) list澜倦,該 list 共有 16081 個(gè)元素,每個(gè)元素大小 5296 個(gè)字節(jié)杰妓。共有 82M藻治。
點(diǎn)擊 value,可以查看 list 中具體的值巷挥。如圖:
發(fā)現(xiàn)正是我們代碼里插入的字符串桩卵。
CPU 高
使用 top 命令,查看哪個(gè)進(jìn)程 CPU 高倍宾,通過 top -p <PID> -H 查看哪個(gè)線程消耗 CPU雏节。使用 jstack 命令打印出 java 進(jìn)程的線程堆棧,通過線程號(hào)找到相應(yīng)的 java 線程高职,結(jié)合 java 代碼钩乍,一般可以找出系統(tǒng)的耗 CPU 代碼。
相關(guān)操作可以參考如下文章:
誰(shuí)偷走了你的服務(wù)器性能
寫在最后
這里只是通過一些栗子說明了性能工具的使用方法怔锌,只是一個(gè)引子寥粹,隨后會(huì)進(jìn)一步介紹如何進(jìn)行性能的優(yōu)化。