常見問題
一卖氨、內(nèi)存飆高
歸根結(jié)底,都是代碼中某一刻的內(nèi)存數(shù)據(jù)過高引起服務(wù)器內(nèi)存增加残炮。
- 文件導(dǎo)入和導(dǎo)出韭赘,針對(duì)文件的讀寫代碼不合理大數(shù)據(jù)常駐內(nèi)存;或者導(dǎo)入/導(dǎo)出并發(fā)量較大势就。
- 存在大表的全表查詢(或者多表級(jí)聯(lián)笛卡爾積查詢)泉瞻,查出的結(jié)果集常駐內(nèi)存脉漏;或者某表查詢數(shù)據(jù)量較大且并發(fā)數(shù)過大(一次查詢數(shù)據(jù)量*并發(fā)數(shù))。
- 類對(duì)象實(shí)例化使用后未釋放或者其它臨時(shí)數(shù)據(jù)未釋放袖牙。
二侧巨、CPU飆高
- 某些線程在做無阻塞的運(yùn)算,簡單的例子while(true)中不停的做運(yùn)算贼陶,沒有任何阻塞刃泡。寫程序時(shí)巧娱,如果需要做很久的計(jì)算碉怔,可以適當(dāng)將程序sleep下。
- 線程上下文切換禁添、當(dāng)啟動(dòng)了很多線程撮胧,而這些線程都處于不斷的阻塞狀態(tài)(鎖等待、IO等待等)和執(zhí)行狀態(tài)的變化過程中老翘。當(dāng)鎖競爭激烈時(shí)芹啥,很容易出現(xiàn)這種情況。(文件讀寫未使用NIO等)
- 序列化和反序列操作過于頻繁或者目標(biāo)數(shù)據(jù)量較大導(dǎo)致資源長時(shí)間占用铺峭。(xml/json)
- 頻繁GC(內(nèi)存異常飆高導(dǎo)致)墓怀,訪問量高時(shí),有可能造成頻繁的GC卫键、甚至FGC傀履。當(dāng)調(diào)用量大時(shí),內(nèi)存分配過快莉炉,就會(huì)造成GC線程不停的執(zhí)行钓账,導(dǎo)致CPU飆高。
- 加密絮宁、解密梆暮。
- 正則表達(dá)式校驗(yàn),大概原因是:Java 正則表達(dá)式使用的引擎實(shí)現(xiàn)是 NFA 自動(dòng)機(jī)绍昂,這種引擎在進(jìn)行字符匹配會(huì)發(fā)生回溯(backtracking)啦粹。
三、客戶端請(qǐng)求阻塞
服務(wù)器CPU和內(nèi)存資源看上去正常窘游,通過JVM監(jiān)控器發(fā)現(xiàn)很多Blocked請(qǐng)求線程唠椭;以下幾個(gè)方法請(qǐng)勿出現(xiàn)在生產(chǎn)代碼中,請(qǐng)求并發(fā)量大的情況下张峰,很容導(dǎo)致請(qǐng)求線程阻塞泪蔫。
- System.out.println(XX)
- logger.debug()
- e.printStackTrace()
- DefaultHttpClient.execute()未設(shè)置連接超時(shí)時(shí)間。
如何定位原因
一喘批、CPU和內(nèi)存情況分析
-
非容器部署時(shí)撩荣,通過top命令查看進(jìn)程占用的CPU和內(nèi)存資源铣揉,找出占用高的進(jìn)程。
-
docker容器部署時(shí)餐曹,通過 “docker stats <容器id或者名稱>”查看cpu和內(nèi)存占用情況逛拱。
-
通過jvisualVM或者JConsole查看JVM的CPU、內(nèi)存和線程情況台猴,如果是CPU飆高或者線程阻塞需要dump線程信息定位問題代碼朽合。
-
線程dump分析,如果開發(fā)人員能力經(jīng)驗(yàn)不足以肉眼分析定位問題饱狂,將dump出來的內(nèi)容拷貝到txt文件中曹步,訪問在線分析平臺(tái)http://fastthread.io/ ,上傳dump文件休讳,點(diǎn)擊分析按鈕讲婚,等待一小會(huì)兒后即可得出分析結(jié)果。重點(diǎn)關(guān)注 “CPU consuming threads”類別下的線程俊柔,點(diǎn)擊具體某個(gè)線程進(jìn)一步定位問題代碼筹麸。
-
如果是內(nèi)存問題推薦使用jmap命令dump heap信息,然后使用MAT分析工具進(jìn)行分析雏婶,移步詳見此文>>物赶。
最新MAT工具下載如果是64位,直接打開會(huì)提示“Java was started but returned exit code=13”報(bào)錯(cuò)留晚,解決方法重新安裝64位的jdk版本酵紫。 我是直接改掉對(duì)應(yīng)啟動(dòng)的jdk路徑下的javaw.exe程序,指向64位的 jdk/bin目錄下的javaw.exe倔丈。
# 導(dǎo)出堆內(nèi)存信息命令:jmap -dump:<dump-options> <pid> jmap -dump:format=b,file=heap.bin 6 # jmap -histo:live <pid> #統(tǒng)計(jì)對(duì)象count 憨闰,live表示在使用 # jmap -histo <pid> >mem.txt #打印比較有多少個(gè)對(duì)象占了多少內(nèi)存的信息,一般重定向文件
題外話:如果是Spring Boot2.0應(yīng)用并且配置了actuator需五,配置開啟堆內(nèi)存和線程的dump接口鹉动,通過瀏覽器訪問即可獲得dump文件。
二宏邮、tomcat日志分析
以上步驟還不能定位問題原因的泽示,只能進(jìn)行應(yīng)用日志的分析。以下介紹一些使用linux命令的分析技巧蜜氨。(當(dāng)然如果已經(jīng)搭建好ELK械筛,可以通過ELK快速篩選異常時(shí)段的請(qǐng)求日志信息)
通過zabbix等監(jiān)控工具,定位應(yīng)用CPU或者內(nèi)存異常時(shí)段飒炎,日志排查以異常時(shí)段前后延長一小段時(shí)間為主埋哟。
題外話:
1.win10支持linux子系統(tǒng),不用特地為了使用linux命令裝個(gè)虛擬機(jī),詳情搜索win10 linux子系統(tǒng)安裝赤赊。
- 或者可以安裝git客戶端程序闯狱,利用其提供的Git Bash客戶端即可享受Linux的相關(guān)命令。
### 分析tomcat的access日志抛计,假設(shè)CPU或內(nèi)存飆升時(shí)段是2019-08-29 16:00:00 ~16:40:00之間
## 截取指定時(shí)段的日志內(nèi)容
# 1.日志時(shí)間格式為 -“2019-08-29 09:25:55606后面跟日志內(nèi)容”
sed -n '/2019-08-29 16:00:00/,/2019-08-29 16:40:00/'p localhost_access_log.2019-08-29.txt > analyse_time.log
# 2.日志時(shí)間格式為 -“22/Feb/2019:15:57:00”
sed -n '/29\/Aug\/2019:16:00:00/,/29\/Aug\/2019:16:40:00/'p localhost_access_log.2019-08-29.txt > analyse_time.log
## 獲取指定時(shí)段QPS(升序排列,看最后幾列最大qps數(shù)據(jù)即可評(píng)判是否并發(fā)數(shù)過大)
cat analyse_time.txt|awk '{Times[$4]++}END{for(a in Times) print a,Times[a]}'| sort -nk2|column -t > analyse_qps.log
## 獲取指定時(shí)段按請(qǐng)求URL分組的QPS(帶url參數(shù))
cat analyse_time.txt|\
awk '{gsub("\\:","_",$4);gsub("\\[","",$4);Times[$4]++;TimesURLs[$4":"$7]++}
END{for(tturl in TimesURLs)
{
split(tturl,INFO,"\\:")
printf("時(shí)間:%s 總訪問量:%d 訪問的URL:%s 訪問量:%d\n",INFO[1],Times[INFO[1]],INFO[2],TimesURLs[tturl])
}
}'|sort -nk2 -nrk4|column -t > analyse_qps_url_by_params.log
## 獲取指定時(shí)段按請(qǐng)求URL分組的QPS(不帶url參數(shù))
cat analyse_time.txt|\
awk '{gsub("\\:","_",$4);gsub("\\[","",$4);split($7,UP,"\\?");Times[$4]++;TimesURLs[$4":"UP[1]]++}
END{for(tturl in TimesURLs)
{
split(tturl,INFO,"\\:")
printf("時(shí)間:%s 總訪問量:%d 訪問的URL:%s 訪問量:%d\n",INFO[1],Times[INFO[1]],INFO[2],TimesURLs[tturl])
}
}'|sort -nk2 -nrk4|column -t > analyse_qps_url.log
## 獲取指定時(shí)段按IP分組的訪問次數(shù)
cat analyse_time.txt|awk '{IPS[$1]++}END{for(a in IPS) print a,IPS[a]}'| sort -nk2|column -t > analyse_ip_total.log
### 通過以上access日志大致可以定位出訪問頻繁的請(qǐng)求url以及進(jìn)一步縮短定位的日志時(shí)段哄孤,根據(jù)上一步驟得到的請(qǐng)求url和時(shí)間分析具體的應(yīng)用日志
## 截取指定時(shí)段的日志內(nèi)容同上
sed -n '/2019-08-29 16:00:00/,/2019-08-29 16:40:00/'p app.info.log > analyse_info_time.log
## 假設(shè)查找到 16:40:30秒下的請(qǐng)求url"app/job/export"請(qǐng)求過于頻繁,可以通過以下定位到具體應(yīng)用日志
cat analyse_info_time.log|grep '2019-08-29 16:40:30'|grep 'app/job/export' >analyse_info_url.log
## 如果應(yīng)用日志過多吹截,比如有app.info.log~app.info.log.20 多個(gè)日志文件瘦陈,如何快速定位要查詢的內(nèi)容在哪個(gè)日志文件中?
find app.info.log.*| xargs grep -ri "SIMPLEJOB-Thread-119" -l
## 查找指定內(nèi)容并顯示前后100行
cat app.info.log|grep -C 100 "SIMPLEJOB-Thread-119"