熱乎的,線上問題排查,拿去面試用!

相信大家面試的時候都會被問到是否有過線上排查問題的經(jīng)歷。
今天我就給大家分享個熱乎的,好吧也不熱了,已經(jīng)過了快兩周了,哈哈哈,不過不影響。
我把這次問題的前因后果都說清楚,大家可以直接當做自己的經(jīng)歷拿去面試用,不用跟我客氣
。
開始咱們的表演。
那是一個陽光明媚的早晨,我卻拖著疲憊的身軀來公司上班。
一到公司,屁股還沒坐熱,一個釘釘就發(fā)了過來:“線上連接池爆了!”
我一聽麻了,啥都不用想,當機立斷直接重啟大法,穩(wěn)如老狗。
好了,一切恢復正常。
全文完。
哈哈,開個玩笑,不過重啟之后應(yīng)用確實是恢復了正常,說明是某個突發(fā)的情況導致連接池爆了。
既然線上已經(jīng)止損了,我們就可以安心的來排查排查這個問題。
連接池滿無非就是...連接都被占用了,一般有兩種情況會導致連接池滿了:
很多長事務(wù),執(zhí)行的慢,導致長時間占用連接,然后別的請求都hang住了 很多短事務(wù),執(zhí)行的快,但是并發(fā)太高,即使時間短,但是架不住量大,得得得得得的就堵住了
這次事故產(chǎn)生的原因就是第二種情況!
DBA 當時發(fā)來一條 sql,說就是這條 sql 被頻繁地執(zhí)行。
我從 kibana 上面搜索了一下這條語句的執(zhí)行情況,發(fā)現(xiàn)其實不止早上8點多有高峰,凌晨竟然有更高峰!
然后再看看這個 sql 執(zhí)行的頻率,這時間排的整整齊齊的,一絲都不帶變的。
并且這條 sql 也不復雜,就是一個帶主鍵的單表查詢,表也不會很大。
所以我斷定這次突發(fā)的情況就是:高并發(fā)下頻繁地請求數(shù)據(jù)庫導致的
那現(xiàn)在問題來了,為啥在某個時候會頻繁的請求這條 sql ?是人性的扭曲還是道德的淪喪?
我直接定位了這條 sql 的請求代碼,發(fā)現(xiàn)這個查詢其實是先走緩存,緩存找不到才會去查數(shù)據(jù)庫。
我仔細看了看代碼,確定了這個業(yè)務(wù)邏輯是用來給前端展示任務(wù)進度的。
就是后臺會跑一個任務(wù),前端需要實時展示一個進度條,這樣用戶使用的時候才不會干著急。
我和前端同事確認了一下,按理前端應(yīng)該 2s 才會請求一次進度,所以即使是直接查詢數(shù)據(jù)庫,也不至于一個用戶如此高頻地調(diào)用請求。
所以我先甩了個鍋,你前端代碼有問題,并沒有遵循 2s 去查,快去看看代碼!
然后我繼續(xù)查詢?yōu)樯?span style="font-weight: 600;color: rgb(60, 112, 198);">這個緩存會消失的問題。
按理來說,具體邏輯是這樣的:
用戶觸發(fā)任務(wù),會塞入進度為 0 的值至緩存中,同時更新任務(wù)的狀態(tài)至數(shù)據(jù)庫。 緩存的過期時間我設(shè)置了一天 后臺任務(wù)在執(zhí)行的時候會實時更新進度 前端調(diào)用接口查詢進度
按照這個邏輯,緩存不可能在查詢的時候不存在的?。?/p>
然后我就開始疑神疑鬼了,難道是 redis 抽了把這個 key 刪了?不至于啊看了看緩存負載也不高。
難道是調(diào)用的 redis client 接口有 bug?過期時間沒給我整對?
然后我模擬了一下,執(zhí)行了一次任務(wù),查詢了一下緩存里的進度,我直接好家伙!

可以看到過期時間竟然只有 5 分鐘?我明明設(shè)置的是一天啊!
我興致勃勃地深入了調(diào)用的 redis client 源碼,想著好家伙,來素材了難道!
看了半天,我反應(yīng)了過來,覺得不可能有問題,要有問題不應(yīng)該只有這個功能會出現(xiàn)這個情況,別的早都爆了。
所以我又把目光移向了更新進度的那個后臺任務(wù)!是不是這個 b 把緩存的過期時間改了?
由于這個任務(wù)不是我寫的,于是我就去找了負責這個任務(wù)的同事,果不其然!
他執(zhí)行任務(wù)更新進度的時候,過期時間設(shè)置的值都為 5 分鐘 !
偽代碼如下:
while(任務(wù)沒結(jié)束)?{
?執(zhí)行邏輯
?更新緩存中的任務(wù)進度++,設(shè)置過期時間為5分鐘
}
他這樣的設(shè)置過期時間也沒毛病,因為當任務(wù)結(jié)束了也就是進度到 100% 了之后,不會再有獲取任務(wù)進度的行為,所以 5 分鐘就讓它過期可以的。
而我之所以設(shè)置 1 天,是想著如果發(fā)生點啥問題可以保留一下案發(fā)現(xiàn)場的數(shù)據(jù)看看。
那按照這個邏輯看下來,應(yīng)該是沒問題的,為啥會發(fā)生這個情況?
我猜想了一下,心里有了個 B 樹。
我問他這個任務(wù)是不是有可能會有阻塞的情況,導致超過 5 分鐘才會更新緩存,這樣在這個阻塞時間內(nèi),前 5 分鐘更新的緩存就過期了,緩存里就沒這個鍵了,此時的查詢就會直接命中數(shù)據(jù)庫,也就重現(xiàn)了上面的那個情況了!
再來看下偽代碼:
while(任務(wù)沒結(jié)束)?{
?//有時候執(zhí)行的時長超過了5分鐘,緩存已過期
?執(zhí)行邏輯?
?更新緩存中的任務(wù)進度++,設(shè)置過期時間為5分鐘
}
他回答到有可能,因為這個任務(wù)會頻繁調(diào)用第三方的接口,并且會包含一些很復雜的合并邏輯,所以有可能超過5分鐘才會繼續(xù)更新緩存。
好了,終于破案了!這也解釋了為什么這個狀況是偶發(fā)的,因為第三方接口是不是會不穩(wěn)定,就可能阻塞超過了五分鐘。
總結(jié)一下:
前端代碼有 bug ,導致頻繁查詢接口(相當于攻擊的頻率了) 后端任務(wù)更新緩存的過期時間為 5 分鐘,但由于業(yè)務(wù)比較復雜且第三方接口不穩(wěn)定,可能處理邏輯耗費的時間為 6 分鐘,導致上一次更新的緩存已經(jīng)過期,使得中間有一段緩存空缺的時間 由于緩存空缺,且前端頻繁查詢,兩者合一導致頻繁查詢數(shù)據(jù)庫 所以連接池爆了
解決辦法也很簡單:讓前端排查下代碼的問題然后修復下,并且也將緩存過期的時間延長至10分鐘,一階段任務(wù)的執(zhí)行時間幾乎不可能超過 10 分鐘。
最后
好了,講完了,這次的問題不難排查,根據(jù)對應(yīng)的現(xiàn)象定位到相應(yīng)的代碼,然后再進行前后端業(yè)務(wù)場景的分析即可。
其實所有的排查都是如此,先止損(看情況不妙就得先重啟),定位代碼,分析情況。
有些難排查的得打 log,有些難重現(xiàn)的還得持續(xù)觀察好幾天。
雖說出了問題總是不好的,但是大家要把握還這樣的機會,及時記錄,這都是以后的談資。
