一次現網問題定位-線程池設置不當,導致流量上去后接口變慢

背景

公司大促活動流量上升,突然一線用戶反饋發消息特別慢,運維已經初步通過監控發現B服務接口大量超時,調用鏈如下圖。
在這里插入圖片描述

發消息接口以前只經過A服務,后面為了防止客服罵人(我們是客服系統),接入了風控。因為產品下面好幾個模塊都要對接風控(外部門的),因此搞了一個公共服務(B服務)去對接風控,B服務本身沒有業務邏輯,基本上是純透傳。

當時業務明確,風控的邏輯耗時不能超過500ms,如果超過500ms則放通,當時這個DFX特性做在了B服務,代碼如下

@RequestMapping(xx)
public ServiceResponse<CheckRspVO> checkTextMsg(request) {Future<Result> aicFuture = hossTaskExecutor.submit(new Callable<Result>() {@Overridepublic Result call() throws Exception {return getAICResult(request);}});return aicFuture.get(500, TimeUnit.MILLISECONDS);
}private Result getAICResult(request) {log.info("rcService: taskId:[{}]",);HttpEntity<String> formEntity = buildHttpEntity<String>(request);try {ResponseEntity<AICRspVO> response = restTemplate.exchange(aicUrl, HttpMethod.POST, formEntity);log.info("rcService: From AIC [{}]");return response.getBody();} catch (RestClientException rtException) {log.error("rcService: Call RiskControl Center Failed: []");}
}

如上所述,B服務接口沒有業務邏輯,就是純透傳請求到風控。只不過為了保證接口500ms能夠返回,使用了異步線程池,并通過Future.get的方法實現500ms超時返回。
上述代碼去掉了一些參數轉換的代碼,整體架子就這些。

定位過程

首先通過監控能明確是風控邏輯導致的接口變慢,只不過不確定是我們自己(B服務),還是風控系統本身導致的。不過自己本能的還是認為是風控系統導致,因為我們本身服務器監控指標沒什么異常,而且B服務也只是透傳,自己沒啥計算量,不至于過載,另外正好出問題的當天有大促活動,業務量增多,很可能是風控系統本身過載了。
為了能把鍋甩出去,還是得找到實質證據。

從日志入手,確認風控有沒有報錯

從日志確實發現調用風控有報錯:“rcService: Call RiskControl Center Failed”
但是由于異常沒有打印出來,并不確認到底是什么問題。因此在仔細查閱一段時間的日志,確認報錯到底是少量報錯,還是大量報錯,試圖從其中找到一些有用信息。(這里要批評一下開發者,異常沒打印出來,導致問題不能快速的確認)。
分析日志的過程中,敏銳的發現一個異常點:getAICResult函數中第一行日志和異常日志,中間隔了5秒鐘,并且所有報錯2者都是隔了5秒鐘。5秒基本就是接口請求的耗時,因為從上面代碼可以看到,并無其他操作(參數組裝轉換的代碼沒貼在上面,但是這個基本不耗時時間)。
接口請求固定花費5秒,這個很像是設置請求超時時間導致的,因為尋找配置,確認到底是什么超時。

public class SpringRestTemplateConfig {// tcp三次握手完成時間@Value("${xx:5000}")private int connectTimeout;// 數據傳輸過程中數據包之間間隔的最大時間@Value("${xx:60000}")private int socketTimeout;

從配置項確認是連接超時,很可能是網絡不通,通過登錄使用telnet命令得以確認。
至此問題得以初步定位(后面找平臺確認是N天前,對網絡做了變更,部分區域恢復了防火墻)。

疑問

通過翻閱日志,發現報錯一致存在, 因此該問題一直有,為什么今天才爆發。

通過回顧代碼,發現已經通過Future.get保證了SLA,就算風控超時,也不會影響B服務及時返回,難道線程池設置的有問題?

解惑

查閱線程池配置如下
corePoolSize為4,workqueue大小4*corePoolSize,即線程池的核心線程,最大線程數量為4,任務隊列大小為16,線程拒絕策略為CallerRunsPolicy。

    @Bean(name = xx)public ThreadPoolExecutor xx(TaskConfig taskConfig, ThreadFactory namedThreadFactory) {return new ThreadPoolExecutor(corePoolSize, corePoolSize, 0, TimeUnit.SECONDS, taskConfig.getWorkQueue(),namedThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());}

從配置上來看,線程數設置的過小,大概率是線程池過載,然后拒絕策略又是在本線程執行(CallerRunsPolicy),導致過載的請求響應時間由0.5m變成了5秒(請求5秒超時)。

證明:
從上述代碼可以看到,只要能夠提交到異步線程處理的都能保證SLA(0.5s)
在這里插入圖片描述
系統能夠保證SLA的QPS為 12個實例*0.8=9.6 一分鐘就是576
對于0.8的解釋:
因為異步線程的最大線程數為4,因此并發量最大為4,單個請求5秒才完成,因此每5秒能完成4個請求,那單個實例的QPS為4/5=0.8,單個實例的請求頻率一旦超過這個數,那么線程池就無法及時處理完,并放入任務隊列,最終任務隊列滿了后,在本線程執行,導致SLA無法得到滿足。

平時調用量統計
在這里插入圖片描述

出問題當天調用量統計
在這里插入圖片描述

當天超時接口統計
在這里插入圖片描述
從上述可以看到,平時QPS也就400到500間,沒有超過576,線程池不會過載,0.5S的SLA能夠保證
出問題時(下午15:50分后)QPS逐漸超過576,線程池過載,問題開始出現。
因為請求的分布并不是絕對的平均(單個容器的QPS超過0.8了),所以15:50分開始,雖然中QPS沒有超過576,但是有少量請求還是變慢了。

總結

線程池的幾個參數意義我這里就不詳細介紹了,網上一大堆。
將請求第三方的邏輯,放到異步線程里面處理,還是比較常見的做法,比如某個接口要調用好幾個第三方,而且各自沒有依賴,會使用異步線程,同時提交對第三方系統的請求,以減小本接口的響應時間。請求第三方屬于IO密集型,本身并不會怎么消耗CPU,因此線程池的線程數可以設置大一點(比如200)。另外一個比較重要的參數就是線程池拒絕策略,一定要想好極端場景的兜底措施,對于本系統,風控邏輯并不重要,線程池超載后可以丟棄,因此可以自定義一個處理策略,丟棄配置打印日志就可以了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70994.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70994.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70994.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【JavaWeb13】了解ES6的核心特性,對于提高JavaScript編程效率有哪些潛在影響?

文章目錄 &#x1f30d;一. ES6 新特性??1. ES6 基本介紹??2. 基本使用2.1 let 聲明變量2.2 const 聲明常量/只讀變量2.3 解構賦值2.4 模板字符串2.5 對象拓展運算符2.6 箭頭函數 &#x1f30d;二. Promise??1. 基本使用??2. 如何解決回調地獄問題2.1回調地獄問題2.2 使…

《幾何原本》命題I.2

《幾何原本》命題I.2 從一個給定的點可以引一條線段等于已知的線段。 設 A A A 為給定點&#xff0c; B C BC BC 為給定線段 連接 A B AB AB&#xff0c;作等邊 △ A B D \triangle ABD △ABD 以 B B B 為圓心&#xff0c; B C BC BC 為半徑作小圓 延長 D B DB DB 交小圓…

java數據結構_Map和Set_9.1

1. 搜索樹 1.1 概念 二叉搜索樹又稱二叉排序樹&#xff0c;它或者是一棵空樹&#xff0c;或者是具有以下性質的二叉樹&#xff1a; 若它的左子樹不為空&#xff0c;則左子樹上所有的結點都小于根結點的值若它的右子樹不為空&#xff0c;則右子樹上所有的結點都大于根結點的值…

Rust Async 并發編程:處理任意數量的 Future 與 Stream

1. Streams&#xff1a;異步數據流 1.1 Streams 與 Iterator 的異同 Rust 的 Iterator 是同步的&#xff0c;通過 next() 方法逐個獲取數據。而 Stream 是 async 版本的 Iterator&#xff0c;它使用 next().await 來獲取數據項。 示例&#xff1a;將 Iterator 轉換為 Stream…

藍橋杯 路徑之謎

路徑之謎 題目描述 小明冒充 XX 星球的騎士&#xff0c;進入了一個奇怪的城堡。 城堡里邊什么都沒有&#xff0c;只有方形石頭鋪成的地面。 假設城堡地面是 nnnn 個方格。如下圖所示。 按習俗&#xff0c;騎士要從西北角走到東南角。可以橫向或縱向移動&#xff0c;但不能斜著走…

3-5 WPS JS宏 工作表的移動與復制學習筆記

************************************************************************************************************** 點擊進入 -我要自學網-國內領先的專業視頻教程學習網站 *******************************************************************************************…

聊聊Java的SPI機制

個人自建博客地址 什么是SPI呢&#xff1f; SPI全稱Service Provider Interface&#xff0c;翻譯過來就是服務提供者接口。調用方提供接口聲明&#xff0c;服務提供方對接口進行實現&#xff0c;提供服務的一種機制&#xff0c;服務提供方往往是第三方或者是外部擴展。 下面…

langchain4j+local-ai小試牛刀

序 本文主要研究一下如何本地運行local-ai并通過langchain4j集成調用。 步驟 curl安裝 curl https://localai.io/install.sh | sh% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed 100 21509 …

什么是“零日漏洞”(Zero-Day Vulnerability)?為何這類攻擊被視為高風險威脅?

正文 零日漏洞&#xff08;Zero-Day Vulnerability&#xff09; 是指軟件、硬件或系統中存在的、尚未被開發者發現或修復的安全漏洞。攻擊者在開發者意識到漏洞存在之前&#xff08;即“零日”內&#xff09;利用該漏洞發起攻擊&#xff0c;因此得名。這類漏洞的“零日”特性使…

鴻蒙 ArkUI 實現 2048 小游戲

2048 是一款經典的益智游戲&#xff0c;玩家通過滑動屏幕合并相同數字的方塊&#xff0c;最終目標是合成數字 2048。本文基于鴻蒙 ArkUI 框架&#xff0c;詳細解析其實現過程&#xff0c;幫助開發者理解如何利用聲明式 UI 和狀態管理構建此類游戲。 一、核心數據結構與狀態管理…

Milvus高性能向量數據庫與大模型結合

Milvus | 高性能向量數據庫&#xff0c;為規模而構建Milvus 是一個為 GenAI 應用構建的開源向量數據庫。使用 pip 安裝&#xff0c;執行高速搜索&#xff0c;并擴展到數十億個向量。https://milvus.io/zh Milvus 是什么&#xff1f; Milvus 是一種高性能、高擴展性的向量數據…

kettle插件-自定義函數-數據脫敏

平常我們在使用kettle抽取數據的時候會涉及到敏感數據邀請脫敏或者進行掩碼的需求&#xff0c;今天我們使用自定義函數插件來實現這些需求。 1、將自定義函數插件&#xff08;kettle-func-plugin.zip&#xff09;解壓后放到kettle的plugins目錄下面&#xff0c;然后重啟服務。…

LeetCode 每日一題 2025/2/24-2025/3/2

記錄了初步解題思路 以及本地實現代碼&#xff1b;并不一定為最優 也希望大家能一起探討 一起進步 目錄 2/24 1656. 設計有序流2/25 2502. 設計內存分配器2/26 1472. 設計瀏覽器歷史記錄2/27 2296. 設計一個文本編輯器2/28 2353. 設計食物評分系統3/1 131. 分割回文串3/2 2/24 …

C++動態與靜態轉換區別詳解

文章目錄 前言一、 類型檢查的時機二、安全性三、適用場景四、代碼示例對比總結 前言 在 C 中&#xff0c;dynamic_cast 和 static_cast 是兩種不同的類型轉換操作符&#xff0c;主要區別體現在類型檢查的時機、安全性和適用場景上。以下是它們的核心區別&#xff1a; 一、 類…

探秘《矩陣之美》:解鎖矩陣的無限魅力

在這個數據驅動的時代&#xff0c;矩陣作為數學中的瑰寶&#xff0c;不僅在理論研究中占據核心地位&#xff0c;更在工程技術、計算機科學、物理學、經濟學等眾多領域發揮著不可替代的作用。今天&#xff0c;讓我們通過中科院大學耿修瑞老師&#xff08;中科院空天信息研究院研…

【MySQL】(2) 庫的操作

SQL 關鍵字&#xff0c;大小寫不敏感。 一、查詢數據庫 show databases; 注意加分號&#xff0c;才算一句結束。 二、創建數據庫 {} 表示必選項&#xff0c;[] 表示可選項&#xff0c;| 表示任選其一。 示例&#xff1a;建議加上 if not exists 選項。 三、字符集編碼和排序…

Vue3實現文件上傳、下載及預覽全流程詳解(含完整接口調用)

文章目錄 一、環境準備1.1 創建Vue3項目1.2 安裝依賴1.3 配置Element Plus 二、文件上傳實現2.1 基礎上傳組件2.2 自定義上傳邏輯&#xff08;Axios實現&#xff09; 三、文件下載實現3.1 直接下載&#xff08;已知文件URL&#xff09;3.2 后端接口下載&#xff08;二進制流&am…

分布式數據存儲:提升系統彈性與性能的技術之路

分布式數據存儲:提升系統彈性與性能的技術之路 在當今數據爆炸式增長的時代,傳統的單機存儲系統已無法滿足大規模、高并發、低延遲的需求。尤其是在大數據、云計算和物聯網的推動下,數據存儲面臨著前所未有的挑戰。分布式數據存儲應運而生,通過將數據分布在多個物理節點上…

在編譯Linux的內核鏡像和模塊時,必須先編譯內核鏡像,再編譯模塊,順序不可隨意調整的原因

問&#xff1a;在編譯Linux的內核鏡像和模塊時,必須先編譯內核鏡像,再編譯模塊,順序不可隨意調整 答&#xff1a;在編譯 Linux 內核和模塊時&#xff0c;必須先編譯內核鏡像&#xff0c;再編譯模塊&#xff0c;順序不可隨意調整。 原因&#xff1a; 模塊依賴內核的頭文件和符…

免費使用 DeepSeek API 教程及資源匯總

免費使用 DeepSeek API 教程及資源匯總 一、DeepSeek API 資源匯總1.1 火山引擎1.2 百度千帆1.3 阿里百煉1.4 騰訊云 二、其他平臺2.1 華為云2.2 硅基流動 三、總結 DeepSeek-R1 作為 2025 年初發布的推理大模型&#xff0c;憑借其卓越的邏輯推理能力和成本優勢&#xff0c;迅速…