唉。。。
大家好,我是程式設計師魚皮。又來跟著魚皮學習線上事故的處理經驗了喔!
事故現場
週一下午,我們的
看到這些我真的非常難受,我們團隊的開發同學也第一時間展開了排查。
簡單看了下前端向後端發的請求,發現所有的請求都一直阻塞,直到超時。直接請求後端伺服器的介面也是一樣的,等了很久都沒有正常返回資料。最關鍵的是,所有介面都阻塞住了,哪怕只是請求個健康檢查介面(後端直接返回 "ok",不查詢資料庫),也無法正常響應。
我們的後端服務是部署在容器託管平臺的,正常情況下如果資源(比如 CPU 和記憶體)佔用超過一定比例,會自動擴容節點來讓服務承載更多的併發請求,但為什麼這次沒有擴容呢?
其實有經驗的朋友應該已經能猜到介面堵死的原因了,下面我帶大家揭開謎團。
事故排查
根據上面的現象,推測大機率是介面層出了問題,而不涉及到業務和資料庫等依賴資源。由於我們的後端使用的是 Spring Boot + 內嵌的 Tomcat 伺服器,而 Tomcat 同時處理請求的最大執行緒數是固定的(預設是 200),所以當同時處理的請求過多,並且每個請求一直沒有處理完成時,所有的執行緒都在繁忙,沒有辦法處理新的請求,就會導致新的請求排隊等待處理,從而造成了介面堵死(遲遲無法響應)的現象。
這裡我用一個簡單的程式來模擬下介面堵死和排查過程。
首先寫一個非常簡單的測試介面,在返回內容前加一個 Thread.sleep,模擬耗時的操作,讓處理請求的執行緒進入較長的等待。
然後更改下 Tomcat 的最大執行緒數為 5,便於我們模擬執行緒數不夠的情況:
啟動專案,在 Thread.sleep 打斷點,然後連續請求 6 次介面。
應該只有 5 次請求會進入斷點,最後一次請求會一直轉圈卡住,沒有執行緒來處理。這樣我們就還原了事故現場。
但以上只是推測,實際線上專案中,怎麼去排查確認 Tomcat 執行緒都阻塞了呢?又怎麼確認是哪個介面或程式碼讓 Tomcat 執行緒阻塞等待了呢?
其實很簡單,首先用 jps -l
命令檢視 Java 後端服務對應的程序 PID:
然後使用 jstack 命令生成執行緒快照,並儲存為檔案。具體命令如下:
jstack <程序PID> > thread_dump.txt
開啟執行緒快照檔案,所有執行緒的狀態一目瞭然,搜尋 http-nio
就能看到 Tomcat 的請求處理執行緒,果然所有的請求處理執行緒狀態都是 TIMED_WAITING
,表示執行緒正在等待另一個執行緒執行特定的動作,但是有一個指定的等待時間。而且能直接看到請求是阻塞在了哪個程式碼位置。
利用這個方法,我們也很快定位到了程式設計導航介面堵死的原因,是發生在一個從資料庫查詢使用者的方法。由於我們昨天下午執行了簡訊群發召回老使用者的動作,導致大量使用者同時訪問程式設計導航並執行這個方法。由於涉及的資料庫查詢操作執行較慢,每個請求都需要等待資料庫查詢出結果後,才能響應資料,下一個請求才能再進來查詢資料庫,就導致大量 Tomcat 請求處理執行緒阻塞在等待資料庫查詢上,再進一步導致新的請求要排隊等待處理。
真相大白了!
如何解決?
其實我們這次遇到的問題就是典型的 “線上連線池爆滿問題”,面試的時候也是經常問的。前面講了怎麼排查此類問題,那如何解決這類問題呢?
首先遇到連線池爆滿的情況,先保護現場,比如按照魚皮上面的操作 dump 執行緒資訊,然後趕緊重啟服務或啟動新的例項,讓使用者先能正常使用。再進行排查分析和最佳化。
如何最佳化線上連線池爆滿問題?首先肯定還是要最佳化造成請求阻塞的程式碼。比如資料庫查詢慢,我們就透過新增索引來提升查詢速度。
還可以增加資料庫連線池的大小,在 Spring Boot 中,預設使用 HikariCP 作為資料來源連線池,而 HikariCP 的 maximumPoolSize
(最大連線池大小)預設值只有 10,顯然是不足以應對高併發場景的。可以把下面的配置數值調大一點:
spring
由於後端請求操作不止有查詢資料庫,所以 Tomcat 執行緒池的最大執行緒數和最小空閒執行緒數也可以按需調整,比如下列配置:
# 設定 Tomcat 最大執行緒數
server.tomcat.threads.max=300
# 設定 Tomcat 最小空閒執行緒數
server.tomcat.threads.min-spare=20
適當調大 Tomcat 的最大執行緒數,可以增加併發請求的處理能力。適當調大 Tomcat 的最小空閒執行緒數,可以確保在併發高峰時刻,Tomcat 能迅速響應新的請求,而不需要重新建立執行緒。
其實我們大多數情況下,線上伺服器(容器)的記憶體利用率是不高的,所以可以根據實際的資源和併發情況,適當地改一改配置。記得多做做測試,因為過高的執行緒數可能導致執行緒排程開銷增加,反而降低效能。
現實
好吧,以上只是我遇到此類問題的解決方案。但現實可能沒那麼理想,其實慢 SQL 這個問題我們在上一次故障時就已經定位到,並且在群內同步了。結果你猜怎麼著,我們團隊的開發同學發了一堆監控的截圖,但是沒有一個人真正去解決了這個問題,這才導致了故障在多日之後重新上演!
一旦發現了問題,就必須要想到儘可能長久支援的解決方案,要不然這監控不是白做了?
為什麼這次事故持續了這麼久呢?也是因為我團隊的開發同學缺少線上問題處理的經驗,在那一通分析,結果忘了恢復服務,過了半個小時使用者還是無法訪問,直到我去提醒。。。
所以這個時候就知道平時背的八股文有多重要了吧?Tomcat 的聯結器配置和效能最佳化也是一道經典的八股文,也是我們
更多
💻 程式設計學習交流:程式設計導航
📃 簡歷快速製作:老魚簡歷
✏️ 面試刷題神器:面試鴨