
先說結論
本次故障的根本原因是應用啟動時未對核心依賴模組的初始化異常進行有效校驗和兜底處理,導致後續請求處理過程中丟擲無法被捕獲的初始化錯誤,進而引發流處理中斷與執行緒阻塞,最終使 HSF 執行緒池耗盡、服務不可用。
具體鏈路:富二方包的啟動由於網路原因超時 ——> 應用啟動過程中,沒有對核心依賴的模組啟動成功做強校驗,導致請求能訪問到對應機器——> 請求進來後,處理邏輯時走到核心類的地方丟擲了ExceptionInInitializerError型別的錯誤 ——> 上層catch的是Exception沒有catch住,錯誤又在Mono流的onErrorResume之前就丟擲走不到onErrorResume中,導致框架層面的Mono流被錯誤終止 ——> 導致訂閱方無法消費到流 ——> 請求返回時訂閱方blockGet沒有設定超時,導致無限等待卡死了HSF的biz執行緒 ——> 最終導致HSF執行緒池滿,應用無法對外提供服務。
綜上所述,本次故障凸顯了應用啟動階段缺乏對關鍵元件狀態的有效檢查及全域性異常處理策略的重要性,下文會展開講述整個排查過程。
背景
在一個風和日麗的晚上,主包正刷著技術貼子,突然上游同學跑過來給我發了一條釘訊息:主幹預發環境呼叫你們的xxx服務報錯,提示HSF執行緒池滿了,已經10多分鐘了,呼叫一直不通。

HSF呼叫報錯堆疊
主包剛收到訊息時,感到非常疑惑,我們的這個服務也是久經戰陣的老兵服務了,在線上也沉澱過許久:經歷過單機上百qps的訪問, rt不過幾十毫秒,執行緒池數量更是基本穩定在50左右。即使伺服器剛啟動的幾分鐘之內出現因為預熱不充分導致的CPU衝高的問題,也很快能恢復,怎麼可能會出現十幾分鐘的執行緒池滿呢?誹謗,一定是誹謗。

線上單機提供服務的HSF資料監控圖
但是當筆者去看了下服務監控卻驚訝的發現,確實,這臺機器執行緒數一直是滿的,不止十幾分鍾了,已經一個多小時了。

問題機器的HSF資料監控圖
於是乎,帶著三分好奇,三分認真和四分漫不經心,主包開始了問題的排查。
排查過程
執行緒現場
既然是執行緒池滿,自然首先要去看執行緒情況,透過thread dump得到的執行緒結果如下:


初步定位
透過執行緒堆疊情況,很快能發現問題:執行緒處於無超時時間的等待狀態PARKED ,透過翻看大部分的HSF biz執行緒的情況,發現基本上所有的biz執行緒都處於這個狀態,且堆疊都是在這個地方,所以基本判斷就是這個堆疊當中導致的問題。透過翻看程式碼發現了這行邏輯:

等待的執行緒的堆疊對應的程式碼
透過翻看文件發現,這個block方法是個無線等待的方法,基本判定就是這個block沒有設定超時時間導致的問題。但是問題就轉換成了為什麼這個地方會無限時間的卡死,而且這個方法僅僅是一個入口方法,底下的邏輯根據業務身份有很多套邏輯,究竟是什麼原因導致執行緒卡死的呢,機器現在不能接受請求了,重啟又擔心現場丟失,於是主包換了個角度去尋找現場。
漸入佳境
主包想了一下既然執行緒卡死了,就看看這些讓執行緒卡死的請求都具有什麼特點,在卡死的附近又發生了什麼。然後主包啪的一下查了機器日誌,很快主包就發現了一個奇怪的日誌:一個呼叫鏈路上的核心類初始化失敗了。

類初始化失敗的日誌
主包於是又去機器上透過arthas的命令對比了這個類在正常機器和異常機器上,使用sc -df獲取到的結果:

報錯機器上命令結果

正常機器上命令結果
這個表現就說明這個類確實沒有載入成功。然後呼叫鏈路裡面的請求涉及到這個類的都丟擲一個java.lang.ExceptionInInitializerError的異常,但是透過觀察日誌發現,只有部分丟擲這個錯誤的請求沒有返回,有部分丟擲這個錯誤的請求,服務是正常返回了。於是進一步排查,發現了沒有返回的請求鏈路上,都出現了這樣一個報錯日誌:

把這個報錯扔給好朋友llm諮詢一下,得到了這樣的回答:
好的,使用者現在遇到了Spring Reactor中的錯誤日誌:“reactor.core.scheduler.Schedulers : Scheduler worker ingroup main failed with an uncaught exception java.lang.ExceptionInInitializerError: null”。我需要分析這個問題的原因和解決方法。
首先,使用者之前已經詢問過類似的排程器異常問題,可能對Reactor框架有一定了解,但這次的錯誤是ExceptionInInitializerError,這可能涉及到類初始化的問題。我需要回憶一下這個錯誤的常見原因。
ExceptionInInitializerError通常發生在靜態初始化塊或靜態變數初始化時丟擲異常。例如,靜態變數的賦值過程中出現錯誤,或者靜態程式碼塊中有異常丟擲。這時候JVM會丟擲這個錯誤,並且可能不會顯示具體的異常資訊,導致除錯困難。
接下來,使用者提到的是在Reactor的排程器執行緒中出現這個錯誤,所以需要考慮是否與排程器配置或相關依賴有關。可能的原因包括:
靜態初始化塊中的錯誤:某個類在載入時,其靜態初始化塊或靜態變數初始化時丟擲異常。例如,Spring的某些配置類或Reactor的元件在初始化時出現問題。
依賴衝突或版本不相容:專案中可能存在多個版本的依賴庫,導致類載入時衝突。比如,不同版本的Spring或Reactor庫可能有不同的靜態初始化邏輯。
資源載入問題:靜態變數可能引用了外部資源(如檔案、網路資源),在初始化時無法載入,導致異常。
環境配置問題:例如,系統屬性或環境變數設定不正確,影響靜態初始化過程。
使用者之前的問題涉及排程器異常處理,但這次是更底層的類初始化錯誤。需要指導使用者如何定位問題。可能的解決步驟包括:
檢查完整的堆疊跟蹤,確定是哪個類的初始化失敗。
檢視依賴項是否有衝突,使用Maven或Gradle的依賴分析工具。
檢查靜態初始化程式碼是否存在潛在錯誤,如空指標或資源訪問問題。
確保所有相關庫的版本相容性,特別是Spring和Reactor的版本匹配。
另外,使用者可能沒有提供足夠的上下文資訊,比如具體的堆疊跟蹤或程式碼片段,因此需要建議使用者提供更多細節以便進一步診斷。同時,可能需要指導使用者如何配置日誌記錄以捕獲更多的錯誤資訊,比如將日誌級別設定為DEBUG或TRACE。
最後,總結可能的解決方案,並給出示例程式碼或配置調整的建議,幫助使用者解決這個問題。
在 Spring Reactor 中,日誌 Scheduler worker ingroup main failed with an uncaught exception 並伴隨 java.lang.ExceptionInInitializerError 通常表明 某個類的靜態初始化塊或靜態變數初始化時丟擲了異常,導致排程器執行緒崩潰。以下是詳細分析和解決方法:
1. 異常含義
ExceptionInInitializerError 是 Java 的一個執行時異常,表示在類的靜態初始化過程中發生了錯誤(如靜態程式碼塊或靜態變數初始化時丟擲異常)。
Scheduler worker ingroup main failed with an uncaught exception 表明該異常發生在 Reactor 的排程器執行緒中,可能導致執行緒終止。
問題已經比較清晰了:如果這個error沒有被正確捕獲,就會導致流進入錯誤狀態,如果沒有正確使用doOnError和onErrorResume,流就會出現終止,從而訂閱者永遠無法獲取到流的結果。
問題就又轉換成了:為什麼有些case下能正常捕獲這個錯誤,有些情況不行,為什麼這個類初始化會報錯以及為什麼類初始化報錯了應用還能正常hsf online 並接受到請求流量?
無法正常捕獲這個錯誤的原因
1.異常捕獲時使用了catch exception 無法捕獲到ExceptionInInitializerError

2.流處理中存在沒有使用onErrorResume的地方

初始化報錯的原因
富二方庫的啟動時需要載入spring beans, 然而在spring讀取xsd檔案時,出現了網路不可達的問題,導致初始化失敗。
content: 2025-06-1619:49:01.867
org.xml.sax.SAXParseException: schema_reference.4: 無法讀取方案文件 'http://www.springframework.org/schema/beans/spring-beans-2.5.xsd', 原因為 1) 無法找到文件; 2) 無法讀取文件; 3) 文件的根元素不是 <xsd:schema>。
at 省略堆疊N行
Caused by: java.net.SocketException: 網路不可達
at 省略堆疊N行
... 78 common frames omitted
正常啟動的原因
這個核心類使用了懶載入的方式,導致即使這個類沒有啟動起來,應用也正常地啟動並且釋出到了註冊中心上提供服務。
真相大白
富二方包的啟動由於網路原因超時 ——> 應用啟動過程中,沒有對核心依賴的模組啟動成功做強校驗,導致請求能訪問到對應機器——> 導致請求進來後,處理邏輯時丟擲了Error型別的錯誤 ——> 上層catch的是Exception沒有catch住,導致框架層面的Mono流被錯誤終止 ——> 導致訂閱方無非消費到流 ——> 請求返回時訂閱方blockGet沒有設定超時,導致無限等待卡死了HSF的biz執行緒——>最終導致HSF執行緒池滿,應用無法對外提供服務。
復現問題
拉了一個專案預發,在同樣的核心類的static程式碼塊裡面直接丟擲異常,然後透過呼叫這個機器,模擬當時的現場,觀察是否能夠復現同樣的問題。果不其然,隨著主包的呼叫,機器執行緒完全卡死,hsf的biz執行緒越來越多,直到執行緒池滿,無法接受任何請求,表現為伺服器癱瘓。
修復辦法
由於這個問題鏈很長,只要將其中的核心點加以修復即可,主要的action有三個:
-
啟動時,將核心富二方包的啟動放入main函式中,如果初始化失敗直接讓應用啟動失敗;
-
在外層catch錯誤時,改成catch Throwable而非catch Exception;
-
訂閱方的blockGet設定合理的超時時間;
總結陳詞
主包之前經常看一個紀錄片《空中浩劫》,裡面講述了很多空難事件,讓主包印象非常深刻的一個點是:很多很大的空難事件,是由一個個微小錯誤累計而成的,軟體行業也是類似的,很多大型的故障比如最近的google cloud認證服務癱瘓導致的cloudfare大面積服務不可用竟然是一個錯誤配置引發的空指標異常引發的一連串連鎖反應導致的。作為一名普通開發,我們能做的只能是開發的時候多想一點,對各種錯誤的假設更多一點,踩過一次坑之後,下次避免碰上類似的情況,如果很多小細節上我們透過程式碼控制住了爆炸半徑,那或許也能減少一個個軟體生產中的“空中浩劫”發生了吧。
全面安全保障:等保 2.0 解決方案
阿里雲安全整合雲平臺等保測評經驗和雲安全產品優勢,聯合等保諮詢、等保測評機構等合作資源,提供一站式等保測評服務,覆蓋等保定級、備案、建設整改及測評階段,助您快速透過等保測評。
點選閱讀原文檢視詳情。