線上問(wèn)題排查指南

前言
最近經(jīng)常有小伙伴問(wèn)我,遇到了線上問(wèn)題要如何快速排查。
這非??简?yàn)工作經(jīng)驗(yàn)了。
有些問(wèn)題你以前遇到,如果再遇到類似的問(wèn)題,就能很快排查出導(dǎo)致問(wèn)題的原因。
但如果某個(gè)問(wèn)題你是第一次遇到,心中可能會(huì)有點(diǎn)無(wú)從下手的感覺(jué)。
這篇文章總結(jié)了,我之前遇到過(guò)的一些線上問(wèn)題排查思路,希望對(duì)你會(huì)有所幫助。
1.OOM問(wèn)題
OOM問(wèn)題在生產(chǎn)環(huán)境中,一旦出現(xiàn),一般會(huì)是非常嚴(yán)重的問(wèn)題,服務(wù)可能會(huì)掛掉。
但是OOM問(wèn)題有多種情況,不同的情況,出現(xiàn)問(wèn)題的原因不一樣。
(1)堆內(nèi)存OOM
服務(wù)器的日志一般會(huì)打印下面的內(nèi)容:
java.lang.OutOfMemoryError: Java heap space這種是出現(xiàn)最多的OOM問(wèn)題。
在Java服務(wù)啟動(dòng)時(shí),可以增加下面的參數(shù):
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof在發(fā)生OOM時(shí),程序會(huì)自動(dòng)把當(dāng)時(shí)的內(nèi)存使用情況,dump保存到指定的文件。
然后使用MAT(Memory Analyzer Tool),或者使用JDK自帶的 Java visualvm,來(lái)分析dump 文件,找出導(dǎo)致OOM 的代碼 。
(2)棧內(nèi)存OOM
出現(xiàn)棧內(nèi)存OOM問(wèn)題的異常信息如下:
java.lang.OutOfMemoryError: unable to create new native thread如果實(shí)際工作中,出現(xiàn)這個(gè)問(wèn)題,一般是由于創(chuàng)建的線程太多,或者設(shè)置的單個(gè)線程占用內(nèi)存空間太大導(dǎo)致的。
這個(gè)時(shí)候需要排查服務(wù)的線程數(shù)量。
推薦使用線程池,可以減少線程的創(chuàng)建,有效控制服務(wù)中的線程數(shù)量。
(3)棧內(nèi)存溢出
出現(xiàn)棧內(nèi)存溢出問(wèn)題的異常信息如下:
java.lang.StackOverflowError該問(wèn)題一般是由于業(yè)務(wù)代碼中寫(xiě)的一些遞歸調(diào)用,遞歸的深度超過(guò)了JVM允許的最大深度,可能會(huì)出現(xiàn)棧內(nèi)存溢出問(wèn)題。
如果生產(chǎn)環(huán)境中,出現(xiàn)了這個(gè)問(wèn)題,可以排查一下遞歸調(diào)用是否正常,有可能出現(xiàn)了無(wú)限遞歸的情況。
(4)GC OOM
出現(xiàn)GC OOM問(wèn)題時(shí)異常信息如下:
java.lang.OutOfMemoryError: GC overhead limit exceededGC OOM一般是由于JVM在GC時(shí),對(duì)象過(guò)多,導(dǎo)致內(nèi)存溢出,建議調(diào)整GC的策略。
在老代80%時(shí)就是開(kāi)始GC,并且將-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)設(shè)置的更合理。
(5)元空間OOM
出現(xiàn)元空間OOM問(wèn)題時(shí)異常信息如下:
java.lang.OutOfMemoryError: MetaspaceJDK8之后使用Metaspace來(lái)代替永久代,Metaspace是方法區(qū)在HotSpot中的實(shí)現(xiàn)。
這個(gè)問(wèn)題一般是由于加載到內(nèi)存中的類太多,或者類的體積太大導(dǎo)致的。
如果生產(chǎn)環(huán)境中出現(xiàn)了這個(gè)問(wèn)題,可以通過(guò)下面的命令修改元空間大小:
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m我在這里列舉了OOM問(wèn)題的最常見(jiàn)的情況。
2.CPU100%問(wèn)題
線上服務(wù)出現(xiàn)CPU100%問(wèn)題,也很常見(jiàn)。
出現(xiàn)這個(gè)問(wèn)題,是由于服務(wù)長(zhǎng)時(shí)間占用CPU資源導(dǎo)致的。
主要原因有下面這幾種:

定位這個(gè)問(wèn)題,可以使用JDK自帶的jstack工具,或者用阿里開(kāi)源的Arthas探測(cè)工具。
3.接口超時(shí)問(wèn)題
不知道你有沒(méi)有遇到過(guò)這樣的場(chǎng)景:我們提供的某個(gè)API接口,響應(yīng)時(shí)間原本一直都很快,但在某個(gè)不經(jīng)意的時(shí)間點(diǎn),突然出現(xiàn)了接口超時(shí)。
導(dǎo)致接口超時(shí)的原因有很多,我們需要挨個(gè)逐一排查。
下面這張圖中給大家列舉出現(xiàn)了,生產(chǎn)環(huán)境接口突然出現(xiàn)超時(shí)問(wèn)題時(shí)的常見(jiàn)原因:

4.索引失效問(wèn)題
不知道你有沒(méi)有遇到過(guò),生成環(huán)境明明創(chuàng)建了索引,但數(shù)據(jù)庫(kù)在執(zhí)行SQL的過(guò)程中,索引竟然失效了。
由于索引失效,讓之前原本很快的操作,一下子變得很慢,影響了接口的性能。
我們可以通過(guò)explain關(guān)鍵字,查看sql的執(zhí)行計(jì)劃,可以確認(rèn)索引是否失效。
如果索引失效了,可能是哪些原因?qū)е碌膯?wèn)題呢?
下面這張圖給大家列舉了常見(jiàn)原因:

5.死鎖問(wèn)題
如果你使用的是MySQL數(shù)據(jù)庫(kù),在生產(chǎn)環(huán)境肯定遇到死鎖問(wèn)題。
死鎖是指兩個(gè)或多個(gè)事務(wù)在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,這些事務(wù)將無(wú)法繼續(xù)向前推進(jìn)。
在Java中,使用MySQL數(shù)據(jù)庫(kù)時(shí),如果遇到MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction異常,意味著數(shù)據(jù)庫(kù)檢測(cè)到了死鎖。
MySQL死鎖通常由以下原因造成:
- 資源競(jìng)爭(zhēng):多個(gè)事務(wù)同時(shí)競(jìng)爭(zhēng)相同的資源,比如都試圖獲取對(duì)方持有的鎖。
 - 循環(huán)等待:事務(wù)之間形成了一種互相等待對(duì)方釋放資源的循環(huán)關(guān)系。
 - 不當(dāng)?shù)氖聞?wù)設(shè)計(jì):事務(wù)執(zhí)行順序不合理、執(zhí)行時(shí)間過(guò)長(zhǎng)等。
 - 并發(fā)操作沖突:在高并發(fā)環(huán)境下,多個(gè)事務(wù)對(duì)同一組數(shù)據(jù)進(jìn)行操作,容易引發(fā)鎖沖突導(dǎo)致死鎖。
 - 索引使用不當(dāng):如果索引設(shè)計(jì)不合理,可能導(dǎo)致事務(wù)在獲取鎖時(shí)出現(xiàn)問(wèn)題。
 
如何減少死鎖問(wèn)題?
- 設(shè)置合理的事務(wù)隔離級(jí)別。
 - 避免大事務(wù)的業(yè)務(wù)代碼。
 - 優(yōu)化sql性能。
 - 增加鎖等待超時(shí)處理。
 - 增加監(jiān)控和分析
 
6.磁盤(pán)問(wèn)題
服務(wù)器磁盤(pán)問(wèn)題是眾多線上問(wèn)題中,最好排查的了。
磁盤(pán)問(wèn)題一般有兩種:
- 磁盤(pán)壞了
 - 磁盤(pán)空間不足
 
如果是磁盤(pán)壞了,運(yùn)維一般在短時(shí)間內(nèi),很難及時(shí)修復(fù)好。
因此,需要及時(shí)更換磁盤(pán)。
如果是磁盤(pán)空間不足。
一般需要登錄到那臺(tái)服務(wù)器, 使用命令:
df -Hl查看當(dāng)前服務(wù)器的磁盤(pán)使用情況。
- 總大小
 - 已使用多少
 - 可用多少
 
最快的解決辦法是,將/tmp文件夾中的文件刪除,可以釋放一些磁盤(pán)空間。
然后找到日志文件,刪除7天以前的日志。
這兩種方式,一般會(huì)釋放不少磁盤(pán)空間,暫時(shí)解決磁盤(pán)空間不足的問(wèn)題。
從常用來(lái)看,我們需要對(duì)服務(wù)器的磁盤(pán)使用情況做監(jiān)控,如果超過(guò)閥值有預(yù)警。
同時(shí)需要需要規(guī)范業(yè)務(wù)系統(tǒng),哪些場(chǎng)景需要打印日志,哪些場(chǎng)景不需要,不應(yīng)該所有的場(chǎng)景,都打印日志。
特別是有些業(yè)務(wù)查詢接口調(diào)用非常頻繁,一次性返回的數(shù)據(jù)很多,這種情況下,會(huì)導(dǎo)致服務(wù)器上的日志迅速膨脹,占用過(guò)多的磁盤(pán)空間。
7.MQ消息積壓?jiǎn)栴}
如果你使用過(guò)MQ消息中間件,在生產(chǎn)環(huán)境肯定遇到過(guò)MQ消息積壓?jiǎn)栴}。
出現(xiàn)這個(gè)問(wèn)題,一般是MQ消費(fèi)者消費(fèi)消息的速度,比MQ生產(chǎn)者生產(chǎn)消息的速度慢。
如果之前一直都是好好的,突然有一天出現(xiàn)了MQ消息積壓?jiǎn)栴}。
可能是下面的原因?qū)е碌模?/p>
- MQ生產(chǎn)者批量發(fā)送消息。
 - 隨著數(shù)據(jù)越來(lái)越多,MQ消費(fèi)者的在處理業(yè)務(wù)邏輯時(shí),mysql索引失效或者選錯(cuò)索引,導(dǎo)致處理消息的速度變慢。
 
如果生產(chǎn)環(huán)境出現(xiàn)MQ消息積壓?jiǎn)栴},先確認(rèn)MQ生產(chǎn)者有沒(méi)有批量發(fā)送消息。
如果有,則可以把MQ消費(fèi)者中線程池的核心線程數(shù)和最大線程數(shù)調(diào)大一些,讓更多的線程去處理業(yè)務(wù)邏輯,提升消費(fèi)能力。
這套方案的前提是MQ消費(fèi)者中,已經(jīng)使用了線程池消費(fèi)消息。
如果沒(méi)有使用線程池,則只能臨時(shí)增加服務(wù)器節(jié)點(diǎn)了。
如果MQ生產(chǎn)者沒(méi)有批量發(fā)送消息,則需要排查MQ消費(fèi)者的業(yè)務(wù)邏輯中,哪些地方出現(xiàn)了性能問(wèn)題,需要做代碼優(yōu)化。
優(yōu)化的方向是:
- 優(yōu)化索引
 - 優(yōu)化sql語(yǔ)句
 - 異步處理
 - 批量處理
 
等等,還有其他的。
8.調(diào)用接口報(bào)錯(cuò)
我們生產(chǎn)環(huán)境的程序,有時(shí)候會(huì)出現(xiàn),之前調(diào)用某個(gè)API一直都是正常的,但突然出現(xiàn)報(bào)錯(cuò)的情況,即返回碼不是200。
那么,這種問(wèn)題,我們?cè)撊绾闻挪槟兀?/p>
(1)返回401
一般生產(chǎn)環(huán)境出現(xiàn)這個(gè)問(wèn)題,是由于沒(méi)有通過(guò)接口的登錄認(rèn)證。
出現(xiàn)這種情況,一般用戶在嘗試訪問(wèn)受保護(hù)的資源前,需要通過(guò)某種形式的身份驗(yàn)證(如登錄),但如果未能正確提供必要的認(rèn)證信息,如Token、用戶名和密碼等。
就會(huì)出現(xiàn)返回碼是401的情況。
(2)返回403
如果生產(chǎn)環(huán)境請(qǐng)求某個(gè)接口,返回碼是403,則說(shuō)明目前沒(méi)有訪問(wèn)資源的權(quán)限。
這種場(chǎng)景跟返回碼是401有區(qū)別。
401著重于認(rèn)證問(wèn)題,即用戶沒(méi)有提供正確的身份驗(yàn)證信息。
而403則是在認(rèn)證成功的基礎(chǔ)上,用戶沒(méi)有足夠的權(quán)限去訪問(wèn)請(qǐng)求的資源。
要解決這個(gè)問(wèn)題,我們需要給接口的調(diào)用方,分配相應(yīng)的訪問(wèn)權(quán)限。
(3)返回404
不用懷疑,你請(qǐng)求的接口地址,現(xiàn)在已經(jīng)不存在了,才會(huì)報(bào)404。
比如有些接口名稱改了,或者接口路徑中/v1/user/query改成了/v2/user/query,版本號(hào)升級(jí)了。
如果沒(méi)有通知所有的接口調(diào)用方,都可能會(huì)出現(xiàn)請(qǐng)求接口返回碼為404的情況。
還有一種可能也會(huì)導(dǎo)致請(qǐng)求接口報(bào)404的問(wèn)題,接口地址之前注冊(cè)到了API網(wǎng)關(guān)中,但API網(wǎng)關(guān)的配置出現(xiàn)了問(wèn)題。
優(yōu)先排查接口url是否修改,然后排查網(wǎng)關(guān)或者Nginx配置是否有問(wèn)題。
(4)返回405
如果請(qǐng)求的接口,返回碼為405,一般是請(qǐng)求方式錯(cuò)誤導(dǎo)致的。
最常見(jiàn)的是:接口只支持post方式,但發(fā)送的卻是get請(qǐng)求。
或者接口只支持get方式,但發(fā)送的卻是post請(qǐng)求。
這種問(wèn)題一般非常好排查和解決。
(5)返回500
如果請(qǐng)求的接口,返回碼為500,一般是出現(xiàn)了服務(wù)的內(nèi)部錯(cuò)誤。
一般網(wǎng)關(guān)層會(huì)對(duì)接口的返回值做一次封裝,不會(huì)返回真正的異常信息。
我們只能查看接口的錯(cuò)誤日志,來(lái)定位和排查問(wèn)題。
建議出現(xiàn)異常時(shí),把接口請(qǐng)求參數(shù)打印出來(lái),方便后面復(fù)現(xiàn)問(wèn)題。
導(dǎo)致這種問(wèn)題的原因有很多,我們只能根據(jù)服務(wù)器上的錯(cuò)誤日志,和相關(guān)的業(yè)務(wù)代碼逐一排查。
(6)返回502
如果請(qǐng)求的接口,返回碼為502,一般是出現(xiàn)了服務(wù)不可用的情況。
有兩種情況:
- 服務(wù)器正在重啟中。
 - 服務(wù)掛掉了。
 
這時(shí)候可以查看一下服務(wù)的監(jiān)控,也可以登錄到服務(wù)器上查看的運(yùn)行狀態(tài)。
大部分情況下,重啟一下服務(wù),可以快速解決問(wèn)題。
然后再根據(jù)服務(wù)器上的日志,可以定位具體的原因,比如:OOM問(wèn)題導(dǎo)致的。
(7)返回504
如果請(qǐng)求的接口,返回碼為504,一般由于網(wǎng)關(guān)或者接口超時(shí)導(dǎo)致的。
接口返回?cái)?shù)據(jù)的耗時(shí),大于網(wǎng)關(guān)設(shè)置的超時(shí)時(shí)間,就會(huì)出現(xiàn)這個(gè)問(wèn)題。
出現(xiàn)這種情況,一般需要優(yōu)化接口相關(guān)的代碼。















 
 
 








 
 
 
 