一次现网问题定位-线程池设置不当,导致流量上去后接口变慢

背景

公司大促活动流量上升,突然一线用户反馈发消息特别慢,运维已经初步通过监控发现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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;迅速…