問(wèn)題復(fù)現(xiàn)
在以前的文章中,小千分享了以前在工作中遇到的線上業(yè)務(wù)BUG解決思路,今天我會(huì)結(jié)合自己的授信中心這個(gè)金融項(xiàng)目,繼續(xù)給大家分析如何對(duì)自己遇到的故障進(jìn)行定位與解決,希望本文可以對(duì)缺乏實(shí)際開(kāi)發(fā)經(jīng)驗(yàn)的小白有所幫助。
其實(shí)要想解決開(kāi)發(fā)故障,通常的解決思路大致如下:
1.分析問(wèn)題,根據(jù)理論知識(shí)+經(jīng)驗(yàn)分析問(wèn)題所在,并將錯(cuò)誤鎖定在一定的范圍內(nèi);
2.通過(guò)錯(cuò)誤日志,快速定位問(wèn)題。線上定位問(wèn)題時(shí),主要是依靠監(jiān)控和日志。
比如小千老師遇到過(guò)這樣一個(gè)問(wèn)題:
線上的金融項(xiàng)目啟動(dòng)后,運(yùn)行速度越來(lái)越慢,一段時(shí)間后直接無(wú)法訪問(wèn),但此時(shí)的內(nèi)存使用率正常,而CPU使用率幾乎滿負(fù)荷。在重啟項(xiàng)目后,又運(yùn)行了一段時(shí)間,項(xiàng)目重復(fù)出現(xiàn)該問(wèn)題。
解決思路
對(duì)于這種線上的故障,我們?cè)撛趺唇鉀Q呢?其解決思路可以按照以下幾個(gè)步驟來(lái)實(shí)現(xiàn)。
其實(shí),大多數(shù)情況下,只要出問(wèn)題,我們都可以利用 df(查看磁盤)、free(查看內(nèi)存)、top(查看CPU) 來(lái)個(gè)素質(zhì)三連,然后再通過(guò)jstack(Java堆棧跟蹤工具)、jmap(Java堆和方法區(qū)的詳細(xì)信息)等工具排查。這些工具的具體使用命令,大家可以自行查閱。
1.top命令
top命令或者其他監(jiān)控?cái)?shù)據(jù),用于查看服務(wù)器的內(nèi)存、cpu的使用情況。
2.jps命令
查看當(dāng)前java程序的進(jìn)程號(hào),假如為:17357,
3.jstat命令
jstat -gc 17357 2000,可以查看jvm的內(nèi)存分配情況,如圖:
接著我們?cè)偻ㄟ^(guò)查看EU和OU、YGC、FGC的變化,來(lái)調(diào)整jvm的內(nèi)存、young區(qū)(edge,s1,s2)、old區(qū)內(nèi)存大小。
可靠建議:
修改JAVA_OPTS='-Xms1024m -Xmx1024m' ,將jvm的最大、最小內(nèi)存設(shè)為系統(tǒng)內(nèi)存的3/4。根據(jù)ygc,調(diào)整young區(qū)中s與edge比例,根據(jù)fgc的頻率調(diào)整young區(qū)和old區(qū)的大小(或比例)。
然后通過(guò)jstack 進(jìn)程id,來(lái)查看線程的死鎖,例如:jstack -l 21733 | more,若出現(xiàn)下圖所示,則是出現(xiàn)了線程死鎖。
4.tomcat優(yōu)化
我們也可以在yml文件中tomcat的配置進(jìn)行優(yōu)化。
server:
port: 9105
tomcat:
threads:
# 處理請(qǐng)求的最大線程數(shù)
max: 350
# 最小的工作線程數(shù)
min-spare: 100
# 等待隊(duì)列的最大隊(duì)列長(zhǎng)度
accept-count: 500
5.設(shè)置數(shù)據(jù)庫(kù)連接池
對(duì)于數(shù)據(jù)庫(kù)服務(wù)(如mysql),dba在部署的時(shí)候,都會(huì)設(shè)置db的最大內(nèi)存和最大鏈接數(shù),開(kāi)發(fā)人員可以暫時(shí)忽略。
另外,數(shù)據(jù)庫(kù)連接池請(qǐng)盡量別用dbch、c3p0等已經(jīng)過(guò)時(shí)的連接池技術(shù),推薦使用阿里巴巴的druid,其相關(guān)的鏈接配置,請(qǐng)參照其github的官網(wǎng)。
具體解決過(guò)程
結(jié)合以上解決思路,接下來(lái)給大家說(shuō)一下我的具體解決過(guò)程。
1.top定位
由于cpu滿負(fù)荷,所以我先通過(guò)top定位到出現(xiàn)問(wèn)題的線程。發(fā)現(xiàn)確實(shí)是我們的java項(xiàng)目所在進(jìn)程吃掉了所有的cpu資源,這時(shí)可通過(guò)jps+jstat來(lái)查看java的gc狀態(tài),進(jìn)一步發(fā)現(xiàn)young gc幾乎是一秒一次,fullgc沒(méi)有。所以接著我又查看了jvm的設(shè)置,young內(nèi)存才設(shè)為512m,我先去掉了這個(gè)配置,而是采用默認(rèn)配置(young:old =1:2)。
在重啟項(xiàng)目之后,young gc基本到了10多秒一次,可項(xiàng)目運(yùn)行一段時(shí)間還是會(huì)卡死。然后我又看了下tomcat的連接池,發(fā)現(xiàn)全部都是默認(rèn)配置(默認(rèn)最大連接數(shù)為50),故先將最大鏈接設(shè)為500,等待隊(duì)列設(shè)為1000,重啟項(xiàng)目,還是出現(xiàn)cpu滿負(fù)荷,系統(tǒng)卡死。
2.排查數(shù)據(jù)庫(kù)服務(wù)的內(nèi)存
接著我查看了輸出的最新日志文件,發(fā)現(xiàn)日志輸出到一個(gè)dao方法之后,在輸出響應(yīng)的sql后就卡住了。接著我通過(guò)mysql鏈接工具直接執(zhí)行,sql卻很快輸出,因此排除數(shù)據(jù)庫(kù)服務(wù)的內(nèi)存不足等硬件問(wèn)題。
3.調(diào)整日志輸出級(jí)別
通過(guò)查看項(xiàng)目的框架,發(fā)現(xiàn)日志用的log4j(同步日志輸出),日志輸出級(jí)別是:INFO,這會(huì)導(dǎo)致項(xiàng)目里面的log輸出非常多,所以我先將log的輸出級(jí)別設(shè)為warn,重新啟動(dòng)項(xiàng)目,項(xiàng)目正常運(yùn)行。
4.優(yōu)化數(shù)據(jù)表
修改日志輸出級(jí)別為warn之后,運(yùn)行了一段時(shí)間發(fā)現(xiàn)系統(tǒng)又卡死了。這時(shí)還是兩個(gè)表的查詢卡死(通訊錄和通話記錄),這兩個(gè)表的存儲(chǔ)量級(jí)都是上億級(jí)別的,項(xiàng)目原有的邏輯是這樣的,用戶上傳通訊錄,需要?jiǎng)h除原來(lái)的通訊錄,再批量插入。這樣一個(gè)大表頻繁的進(jìn)行刪除與批量插入,很容易導(dǎo)致IO響應(yīng)慢。第一步,我先通過(guò)建立唯一索引,利用insert ignore into來(lái)減少該表的IO操作,接著重新啟動(dòng)項(xiàng)目,系統(tǒng)正常運(yùn)行。
5.添加索引
加好索引優(yōu)化了sql語(yǔ)句之后,系統(tǒng)還是偶爾會(huì)出現(xiàn)卡死狀態(tài)。這時(shí),我通過(guò)dba搜索慢查詢,發(fā)現(xiàn)通話記錄和通訊錄表中有一個(gè)排序查詢,該sql的末尾使用了order by update_time desc,但update_time沒(méi)有添加索引,這就導(dǎo)致該表的查詢至少要2秒以上。所以我將sql改為了order by id,查詢就正常了,重啟項(xiàng)目,系統(tǒng)正常運(yùn)行。
6.解決線程死鎖
至此,系統(tǒng)還是偶爾出現(xiàn)卡死現(xiàn)象(cpu爆表),只是頻率小了很多。這時(shí)我懷疑是有線程死鎖了,從而導(dǎo)致cpu爆表。我通過(guò)運(yùn)維查找linux線程,終于發(fā)現(xiàn)確實(shí)有一個(gè)線程出現(xiàn)了死鎖,里面的信息顯示是c3p0的連接池線程。我又通過(guò)查找資料,發(fā)現(xiàn)使用c3p0作為數(shù)據(jù)庫(kù)連接池,經(jīng)常會(huì)出現(xiàn)鏈接池卡死的問(wèn)題,所以我趕緊將項(xiàng)目的連接池切換為durid,然后重啟項(xiàng)目,項(xiàng)目運(yùn)行正常。
7.新增服務(wù)器節(jié)點(diǎn)
但系統(tǒng)在進(jìn)件量較大時(shí),依然有一定的幾率出現(xiàn)卡死現(xiàn)象,最好考慮是當(dāng)時(shí)的項(xiàng)目環(huán)境采用的是單機(jī)部署,所以最后協(xié)調(diào)運(yùn)維新增了兩個(gè)服務(wù)器節(jié)點(diǎn),至此項(xiàng)目運(yùn)行完全正常。
針對(duì)線上服務(wù)器故障的解決思路和過(guò)程,百澤老師給大家提供了以下幾個(gè)可靠建議:
系統(tǒng)框架太過(guò)于老舊時(shí),可能會(huì)引發(fā)一系列的項(xiàng)目問(wèn)題,所以適當(dāng)升級(jí)項(xiàng)目的技術(shù)是有必要的;
一些大表一定要進(jìn)行拆分,否則在高并發(fā)的環(huán)境中,數(shù)據(jù)庫(kù)的IO會(huì)遇到瓶頸;
減少一些不必要的日志輸出,日志輸出組件盡量是異步輸出的;
一些高頻率查詢的字段,盡量加上索引、組合索引,一些慢查詢的sql優(yōu)化也很重要;
服務(wù)盡量單獨(dú)部署。