线程池:故障梳理总结

简介: 本文从故障与技术双重视角,总结线程池满导致服务不可用的常见场景及解决方案。涵盖数据库慢查询、热更新、DDL锁表、连接池配置不当等问题,结合真实案例剖析根因,并提出fast-fail、流控背压、合理重试等最佳实践,助力开发者提升系统稳定性。

背景
团队新同学反馈想学习了解线程池类的故障,由笔者做梳理和分享(所梳理的故障材料来自团队多年积累的故障复盘报告),内容对外部开发者来说也有借鉴意义,因此发出来希望能帮助到一些开发者。
我会从故障视角和技术视角两个角度来分析总结,故障视角可以看到现象和血淋淋的教训,而技术视角可以透过现象看到本质更进一步可以看看如何避免。
故障视角
笔者经历了很多大大小小的故障,总结来看确实有很多线程池满导致服务不可用的故障。一般线程池满只是结果,诱因还是系统某个地方慢了,最典型的一类 Case 就是数据库 SQL 慢导致数据库连接池满,数据库连接池满进而导致对外提供服务的业务线程池(如 Dubbo 线程池)满,线程池一旦满了,就大概率无法响应新的请求,或者能响应新的请求但一直在排队无法及时处理请求导致请求耗时增加,在用户侧看来就变成了超时-服务不可用。
下面贴一些典型的常见 Case,开发同学基本一看就懂并不神奇。
数据库相关
热更新
在事务里热更新同一条数据容易引发锁等待造成慢 SQL,常见于一些 update count,update quota 类的业务场景。
故障案例1:某次压测对 DB 产生瞬时 60w+ QPS 的压力,期间同一条数据(更新 count 字段)在事务里大量热点更新导致了行锁争抢产生慢 SQL。
故障案例2:几个大用户高并发操作,其中涉及单条热点数据在事务里的更新,排查发现单次更新耗时高达5-6秒,积压的线程引起 Dubbo 对外服务线程池堆积,最终线程池满导致无法对外服务。
线下模拟测试发现 1200 并发进行热点数据的更新(在特定的数据库版本和配置下),开启事务需要1分钟,不开启事务需要3秒。
大表加字段
DDL 变更有多种方式,最原始的方式会造成锁表问题进而引发大量相关联 SQL 锁等待产生慢 SQL;DDL 变更建议走 Online DDL。历史上出现过的一些锁表的 Case 应该是没有走 Online DDL,也可能当时数据库版本不支持 Online DDL。
故障案例:大表添加字段未采用 Online DDL,在最后阶段会对表加 Metadata Lock 原子锁,使得大量相关 SQL 锁等待产生慢 SQL,进而快速打满应用线程池。
索引没走对(走了主键全表扫描)
常见于 order by id limit 场景,就算 where 条件里的字段有索引还是有可能走全表扫描。可以通过 IGNORE INDEX(PRIMARY),FORCE INDEX(idx_xxx) 等方式来解决。
故障案例:凌晨 3 点多突然收到报警数据库 CPU 100%,排查发现某查询 SQL 走了主键索引触发了全表扫描(SQL 样例为:where a= and b= and c= and d= order by id desc limit 20,当时只有 idx_a_b_e 的联合索引),期间在数据库运维平台手工无差别限流 SQL 有所缓解但很快 CPU 又会飚上来,也尝试了物理删除一些无效数据减少数据量,多管齐下,最后通过临时增加一个 idx_a_b_c_d 新的全字段覆盖的索引止血。
深分页
数据量大时深分页引发慢 SQL 也是个常见的经典问题。解法可以是使用 NexToken 或者叫游标的方式查询,目前阿里云有很多 OpenAPI 已经提供了 NextToken 的查询方式。
故障案例:某账号(数据量巨大)调用某查询接口分页查询引发慢 SQL 导致数据库连接池满进而导致 Dubbo 线程池满无法对外服务,紧急限流该账号对该接口的调用后恢复。
调用量大
故障案例1:故障恢复后,短时间重试待处理任务到单机执行,量太大导致单机线程池满导致服务受损。
解法:系统层面需要做一定的限流策略,单机任务瓶颈时应切换到网格型任务。
故障案例2:压测未预热,直接一次性并发到压测值导致线程池满,导致数据库有很多事务等待的慢 SQL。
解法:压测应按照一定节奏逐步上量,观察系统负载并及时暂定,而不是开局就决战。
其他
故障案例:查询没加 Limit 导致应用 Full GC
该 Case 不涉及线程池满问题,但笔者觉得有一定的代表性因此也分享下。不管是查询还是删除还是更新数据,不管是代码还是日常的 SQL 订正,建议都增加 Limit 来兜底保护自己,缩小影响面。
技术视角
线程池类的故障,一般都是某个地方慢了堵了,从技术角度大多是:
1、远程调用 IO 慢导致耗时增加;
2、计算密集型应用 CPU 飙升导致耗时增加;
3、自定义业务线程池满造成排队等待导致耗时增加;
其中 2 不算常见,笔者也遇到过,发生于某 CPU 密集运算的应用系统,突增的高并发请求引起 CPU 100%;其中 1 比较常见,一般远程调用有:Dubbo、Http、DB、Redis,这些实践中都会使用连接池来与远程服务交互,凡是连接池都是有共性的,有两个需要关注的点:
1、尽量减少远程调用本身的 超时时间 以实现 fast-fail 快速失败。一般是设置 ConnectionTimeout 即握手时间 和 SocketTimeout 即业务执行超时时间。
2、在连接池满了以后,获取新的连接的 超时时间 也需要设置的小一些以实现 fast-fail 快速失败,这个是很容易忽略的一个点。如 Druid 里设置 MaxWait,Http 连接池里设置 ConnectionRequestTimeout。
下面列一下各个连接池需要关注的点。
Dubbo 线程池
1、线程池做好隔离,避免互相影响
如内部运维接口和对外服务的接口做隔离。
对外服务里核心接口和非核心接口做隔离。
2、Dubbo consumer 侧设置 timeout,根据 fast-fail 理念设置的越小越好;provider 侧的 timeout 仅仅是起到声明的效果供 consumer 参考,无实际超时杀线程的作用。
Http 连接池
1、设置 ConnectTimeout、SocketTimeout、ConnectionRequestTimeout
故障案例:某次发布的代码引入了一个 SDK,该 SDK 集成了 HttpClient,但并没有设置 ConnectionTimeout,在某次网络抖动发生时,Http 连接池被迅速打满,进而导致业务线程池满导致服务受损。
2、DefaultMaxPerRoute 太小也容易导致阻塞。
故障案例:某 SDK 默认设置的 128,在某次压测中发现客户端耗时较高,但服务端耗时并无波动,排查后怀疑是 DefaultMaxPerRoute 太小导致的阻塞,调大后问题解决。
数据库连接池 Druid
1、设置 ConnectTimeout、SocketTimeout。
故障案例:凌晨 1 点多收到 API 成功率降低报警,排查发现部分 SQL 执行超时,原因是数据库发生了主备切换,进一步排查发现应用侧对数据库连接池没有设置 SocketTimeout 导致切换前的老的连接不会被超时 Kill 导致相关 SQL 执行超时,直到 900秒系统默认超时后才会断开连接再次重连。
2、设置 TransactionTimeout 即事务超时时间,事务就是一把锁,超时时间越长锁越久,导致不在事务里的相关 SQL 锁等待导致性能差。
故障案例:在某次变更时由于代码有 bug 导致事务未提交,同时由于事务没设置超时时间,导致大量相关 SQL 超时服务受损。
3、设置 Ibatis 的 defaultStatementTimeout、queryTimeout。
4、设置 MaxWait:获取新连接的等待超时时间。
小插曲:之前 Druid 默认设置的 60 秒,后来笔者与作者有过沟通反馈这个默认值太长容易坑大家,后来发现已经改为了 6 秒[1]
自定义线程池
1、线程池设置的队列过长容易造成阻塞影响吞吐。
2、future.get,默认没有超时时间,需显式传入。
故障案例:Dubbo 线程池满报警,排查后发现是业务代码里使用了 future.get 没有设置超时时间,同时线程池的拒绝策略设置的是 DiscardPolicy,会导致在线程池满后新的任务被丢弃时 future.get 阻塞,进而导致 Dubbo 线程池满服务受损。
Redis连接池
1、设置 Jedis pool MaxWait,与 Druid 的 MaxWait 类似,也与 Http 连接池的 ConnectionRequestTimeout 类似。
2、设置 ConnectionTimeout、SocketTimeout,与 Druid/Http 连接池的类似。
总结
fast-fail 理念
1、本质上是不浪费系统资源,一些超时时间设置过长其实是在做无效的 IO 等待。
2、有一些个人的经验值贴一下:ConnectionTimeout 建议1-3 秒最佳,最大不超过 5 秒。SocketTimeout 根据业务请求时间情况设置建议最大不超过 10 秒,MaxWait/ConnectionTimeout 建议 3~5 秒,最大不超过 6 秒。
保护好自己:流控/背压
1、数据库后台运维平台设置自动限流,紧急情况下收到预警后第一时间手动执行限流。
2、实现 单机维度、集群维度(Region/AZ)、用户维度、接口维度 流控。
3、消息中间件拉取消息的 Client 实现背压机制
谨慎重试
Retry 会加速系统雪崩,AWS 有一篇博客介绍了相关的经验,Link> [2]。核心要点如下:
不在最上层自动重试,在单个节点里重试
令牌桶控制重试的速率
定时、周期性的作业需要打散,分散高峰。这块我们也遇到过类似的故障案例:
故障案例1:某客户端曾经出过一个类似故障:客户端的定时心跳同一秒发送到服务端,导致服务端扛不住,此类情况需适当打散。
故障案例2:某系统大量定时任务都是整点执行,一瞬间对系统压力过大引发线上问题,定时任务的周期需适当打散。
最后,本文有很多血淋淋的教训,大多是常见问题,本文肯定有不全面的地方,欢迎评论区多多指教。

相关文章
|
2月前
阿里云产品十二月刊来啦
全新万相2.6系列模型正式发布,通义百聆语音交互模型开源,PAI 模型评测新支持双模型离线竞技功能|产品十二月刊
159 12
阿里云产品十二月刊来啦
|
2月前
|
人工智能 JSON 数据挖掘
大模型应用开发中MCP与Function Call的关系与区别
MCP与Function Call是大模型应用的两大关键技术。前者是跨模型的标准协议,实现多工具动态集成;后者是模型调用外部功能的机制。MCP构建通用连接桥梁,支持跨平台、热插拔与细粒度管控,适用于复杂企业场景;Function Call则轻量直接,适合单模型快速开发。二者可协同工作:模型通过Function Call解析意图,转为MCP标准请求调用工具,兼顾灵活性与扩展性。未来将趋向融合,形成“解析-传输-执行”分层架构,推动AI应用标准化发展。
Springboot+JPA+Sqlite整合demo
Springboot+JPA+Sqlite整合demo
611 0
|
2月前
|
前端开发 数据可视化
什么是低代码
该界面为低代码平台,支持通过拖拽方式快速生成前端表单页面,提升开发效率。包含可视化操作与组件配置,适用于快速搭建业务表单。参考文档详见附件。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
AI切文章就像切西瓜:递归字符分割让机器懂你心
你有没有试过给ChatGPT发一篇超长文章,结果它说'太长了,看不完'?就像让人一口吃下整个西瓜一样不现实!递归字符分割技术就像一个贴心的切瓜师傅,知道在哪里下刀才不会破坏瓜的甜美。掌握这项技术,让你的AI应用从'消化不良'变成'营养吸收专家'。#人工智能 #文本处理 #自然语言处理 #机器学习
189 4
|
2月前
|
Java 测试技术 Linux
生产环境发布管理
本文介绍大型团队如何通过自动化部署平台实现多环境(dev/test/pre/prod)高效发布与运维。涵盖各环境职责、基于Jenkins+K8S的CI/CD流程、分支管理、一键发布及回滚机制,并结合Skywalking实现日志链路追踪,提升问题定位与修复效率,助力企业级DevOps落地。
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
AI群策群力术:让多个大模型一起干活不摸鱼
想让AI回答更准确?别指望一个模型包打天下!就像做菜找多个大厨试味,提示词集成(Prompting Ensembling)让多个提示词协同作战,通过民主投票选出最佳答案。从自一致性(Self-Consistency)到多样化推理(DiVeRSe),掌握这些技巧让你的AI应用准确率飙升!#人工智能 #提示词工程 #机器学习 #AI优化
201 3
|
2月前
|
Java 测试技术 API
从Google线上故障,谈灰度发布的重要性
2025年6月12日,Google Cloud因未灰度发布的新功能引发空指针异常,导致全球服务中断超7小时。故障暴露了配置管理的重大隐患。本文深入分析根因,详解基于Nacos的IP与标签灰度发布方案,强调通过配置中心实现渐进式发布的必要性,为高可用系统提供实战指南。
|
2月前
|
运维 Devops 开发工具
生产环境缺陷管理
git-poison基于go-git实现,通过“投毒-解毒”机制在分布式环境中精准追溯、管理bug,避免多分支开发中bug修复遗漏问题。它不依赖人工沟通,自动卡点发布流程,有效阻塞带未修复bug的版本上线,已在大型团队落地一年,显著降低协同成本与生产风险。
|
11月前
|
XML 存储 分布式计算
【赵渝强老师】史上最详细:Hadoop HDFS的体系架构
HDFS(Hadoop分布式文件系统)由三个核心组件构成:NameNode、DataNode和SecondaryNameNode。NameNode负责管理文件系统的命名空间和客户端请求,维护元数据文件fsimage和edits;DataNode存储实际的数据块,默认大小为128MB;SecondaryNameNode定期合并edits日志到fsimage中,但不作为NameNode的热备份。通过这些组件的协同工作,HDFS实现了高效、可靠的大规模数据存储与管理。
1280 70

热门文章

最新文章