本文來自: PerfMa技術社區PerfMa(笨馬網絡)官網java
模型服務平臺的排序請求出現較多超時狀況,且不定時伴隨空指針異常。web
召回引擎擴大了召回量,致使排序請求的item數量增長了。服務器
基於XGBoost預測的全排序模型。網絡
web-rec-model:模型服務平臺。用於管理排序模型:XGBoost、TensorFlow、pmml....召回模型:item2item,key2item,vec2item....等模型的上下線、測試模型一致性、模型服務等。多線程
一、以下圖所示,一次排序請求流程包含:特徵獲取、向量獲取、數據處理及預測。以上提到的三個步驟均採用多線程並行處理,均以子任務形式執行。每一個階段中間夾雜這數據處理的流程,由主線程進行處理,且每一個階段的執行任務均爲超時返回,主線程等待子線程任務時,也採用超時等待的策略。(同事實現的一個樹形任務執行,超時等待的線程框架)併發
二、特徵數據閉環:該步驟爲異步執行,將排序計算使用到的特徵及分數,模型版本等信息記錄。後續做爲模型的訓練樣本,達到特徵閉環。框架
三、一次排序請求中,特徵獲取及向量獲取爲網絡IO(IO密集型任務),超時可直接響應中斷,線程可快速返回。數據處理及模型爲計算步驟(CPU密集型任務)。異步
四、當前請求耗時狀況:特徵與向量的獲取階段耗時均爲5-8ms,數據處理及模型預測階段耗時平均在10ms左右。測試
一、首先是調用方:推薦策略平臺,監控報警排序請求的超時數量變多(調用方超時時間爲300ms),且從監控上看發現排序服務的耗時明顯變長:50ms+。正常高峯期的指望值爲50ms如下。spa
二、其次排序服務告警出現大量超時錯誤。
三、第三根據錯誤信息定位到該錯誤信息來自於數據處理及模型預測階段。
四、除了超時變多之外,服務中會出現偶發性的空指針異常。
一、首先解決空指針這類低級錯誤。
二、根據錯誤提示找到對應的代碼,此處就不粘貼代碼了,作一個簡單的代碼解釋。代碼邏輯爲:從Map<String,Object>中根據特徵key獲取特徵值進行計算。
三、疑惑點出現,首先該Map<String,Object>用於存放特徵及向量鍵值對,且key均作了空值計算兼容。特徵或者向量在查詢到空值時,會在Map<String,Object>中放入一個對應的默認值。通過反覆的代碼確認,報錯信息對應的代碼不可能出現漏放默認值的狀況。
四、藉助Arthas的watch命令,監控空指針異常的入參。方便後面作模擬請求還原現場。
五、根據報錯時的信息進行模擬請求。嘗試N次,且使用不一樣的報錯數據進行嘗試,均未重現事故。
六、此時懷疑是多線程併發進行數據處理及預測時,發生對Map<String,Object>進行修改的動做,致使部分鍵值對丟失。
七、反覆檢查代碼,肯定數據處理及預測均爲只讀動做,不會對Map<String,Object>進行任何鍵值對的刪改。
八、線索中斷,排查一度擱置。
一、借用Arthas進行報錯觀察:使用watch命令,依靠-e參數(指定報錯觸發打印)以及-x n 參數(打印方法入參及返回值數據層數)
二、根據觀察,發現Map<String,Object>中丟失的均爲向量鍵值對。
三、找到問題:在排序請求流程圖中,在主線程進行分數歸一化時,會fork子線程異步作特徵數據進行壓縮寫入kafka。因爲Map<String,Object>中存在大量的向量數據,致使保存數據過冗餘的狀況。此處的作法是先去除全部的向量數據,再進行保存。
四、可是該動做是發生在數據處理及模型預測後的,爲什麼還會由於Map<String,Object>中刪除鍵值對致使空指針異常呢。
五、此時懷疑是數據處理及模型預測階段,多線程任務還沒完成時,主線程已經等候超時返回了。
一、仍是觀察超時日誌。
二、發現請求已經返回後,纔出現空指針異常。那基本就能夠驗證以上的想法了。
一、翻看使用的多線程框架(同事實現),主線程超時等待子線程任務。主線程超時返回後,沒有通知子線程任務取消。因此才發生請求已返回,特徵數據異步落地後,偶發性出現晚到的空指針異常的狀況。以下圖,主線程超時返回後,只取消主線程任務。
二、解決思路:主線程超時返回後,中斷子任務(取消子任務)。因爲java的中斷機制爲軟中斷,通常是經過中斷標誌位進行線程中斷協做的。固然IO或者sleep的中斷由系統幫咱們作了中斷能夠快速返回。對於CPU密集型的任務,是須要使用者在合適的計算點上作標誌位判斷,肯定是否已中斷結束任務。以這種協做的方式達到中斷。(此處可能有部分理解不當)
三、修改多線程框架,在主線程超時返回後,修改子線程中斷標誌位。
四、在計算流程中加入線程中斷檢查,若是被中斷則提早結束計算。
一、修改發版後,空指針沒再出現。(其實該空指針是不影響排序結果,由於結果已是錯的,該異常只是附帶的蟲子而已)
二、超時請求減小,高峯期的超時數據減小三分之一,50ms+的排序請求有明顯減小。
一、主線程等待子任務的場景下,若是主線程超時返回了。需通知子線程結束執行的任務。首先,主線程返回了,表示子任務已被丟棄。繼續執行都是在作無用的計算,佔用計算機資源。也不是說佔着茅坑不拉屎,而是拉了沒人要。應該儘可能減小服務器資源用在不必的消耗上。
二、該服務在數據處理及預測階段使用的線程池隊列爲SynchronousQueue,若是不瞭解SynchronousQueue的話能夠簡單理解爲一個0長度的隊列。任務進池子時必需要有線程進行對接。與常規的BlockingQueue不一樣的是,任務在池子中不會堆積,對於任務的快速響應比較友好。可是也由於若是沒有空閒的線程,則會不停建立線程直到最高線程數限制而觸發丟棄策略。在該項目問題中,因爲部分子任務在主線程返回後仍然在執行。新的請求進來後,會出現沒有空閒線程的狀況,致使池子建立新線程接任務。對於CPU密集型任務來講,過多的線程數對服務來講是另外一種負擔,畢竟線程切換的代價仍是比較大的。這就套入死循環了。(我的理解,如表述有誤,還望指正)
歡迎關注 PerfMa 社區,推薦閱讀