低并发编程|如何用720个请求让后端服务器瘫痪

简介: 本次故障因应用启动时未有效校验核心依赖模块初始化异常,导致后续请求处理中抛出无法捕获的错误,引发线程阻塞,最终耗尽HSF线程池,服务不可用。排查发现类初始化失败且异常未被正确处理,结合线程无限等待问题,确认为依赖初始化异常与流处理中断所致。修复措施包括加强启动校验、捕获Throwable及设置合理超时。总结指出,系统稳定性需从细节入手,防微杜渐,避免连锁故障。

先说结论

本次故障的根本原因是应用启动时未对核心依赖模块的初始化异常进行有效校验和兜底处理,导致后续请求处理过程中抛出无法被捕获的初始化错误,进而引发流处理中断与线程阻塞,最终使 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得到的线程结果如下:

线程总览

HSF biz线程的堆栈情况

初步定位

通过线程堆栈情况,很快能发现问题:线程处于无超时时间的等待状态PARKED ,通过翻看大部分的HSF biz线程的情况,发现基本上所有的biz线程都处于这个状态,且堆栈都是在这个地方,所以基本判断就是这个堆栈当中导致的问题。通过翻看代码发现了这行逻辑:

等待的线程的堆栈对应的代码

通过翻看文档发现,这个block方法是个无线等待的方法,基本判定就是这个block没有设置超时时间导致的问题。但是问题就转换成了为什么这个地方会无限时间的卡死,而且这个方法仅仅是一个入口方法,底下的逻辑根据业务身份有很多套逻辑,究竟是什么原因导致线程卡死的呢,机器现在不能接受请求了,重启又担心现场丢失,于是主包换了个角度去寻找现场。

渐入佳境

主包想了一下既然线程卡死了,就看看这些让线程卡死的请求都具有什么特点,在卡死的附近又发生了什么。然后主包啪的一下查了机器日志,很快主包就发现了一个奇怪的日志:一个调用链路上的核心类初始化失败了。

类初始化失败的日志

主包于是又去机器上通过arthas的命令对比了这个类在正常机器和异常机器上,使用sc -df获取到的结果:

报错机器上命令结果

正常机器上命令结果

这个表现就说明这个类确实没有加载成功。然后调用链路里面的请求涉及到这个类的都抛出一个java.lang.ExceptionInInitializerError的异常,但是通过观察日志发现,只有部分抛出这个错误的请求没有返回,有部分抛出这个错误的请求,服务是正常返回了。于是进一步排查,发现了没有返回的请求链路上,都出现了这样一个报错日志:

把这个报错扔给好朋友llm咨询一下,得到了这样的回答:

好的,用户现在遇到了Spring Reactor中的错误日志:“reactor.core.scheduler.Schedulers : Scheduler worker in group 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 in group main failed with an uncaught exception 并伴随 java.lang.ExceptionInInitializerError 通常表明 某个类的静态初始化块或静态变量初始化时抛出了异常,导致调度器线程崩溃。以下是详细分析和解决方法:

1. 异常含义
ExceptionInInitializerError 是 Java 的一个运行时异常,表示在类的静态初始化过程中发生了错误(如静态代码块或静态变量初始化时抛出异常)。
Scheduler worker in group 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 [2101c6a117501285410754275d2333] [9.1.2.2.1.1.2.1.2.1.3.18]  WARN 4754 --- [wrappedProductBoundElasticScheduler-7] o.s.b.f.xml.XmlBeanDefinitionReader      : Ignored XML validation warning
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大面积服务不可用竟然是一个错误配置引发的空指针异常引发的一连串连锁反应导致的。作为一名普通开发,我们能做的只能是开发的时候多想一点,对各种错误的假设更多一点,踩过一次坑之后,下次避免碰上类似的情况,如果很多小细节上我们通过代码控制住了爆炸半径,那或许也能减少一个个软件生产中的“空中浩劫”发生了吧。


来源  |  阿里云开发者公众号

作者  |  渐之

相关文章
|
9月前
|
消息中间件 缓存 负载均衡
构建高效可扩展的后端架构:从设计到实现
本文探讨了如何构建高效、可扩展的后端架构,涵盖需求分析、系统设计、实现与优化全过程。内容包括微服务、数据库设计、缓存与消息队列等关键技术,并涉及API设计、自动化测试、CI/CD及性能优化策略,助力打造高性能、易维护的后端系统。
|
8月前
|
存储 缓存 监控
一次缓存引发的文件系统数据不一致问题排查与深度解析
本文详述了一次由自研分布式文件系统客户端 EFC 的缓存架构更新所引发的严重数据不一致问题的完整排查过程。
一次缓存引发的文件系统数据不一致问题排查与深度解析
|
8月前
|
SQL 关系型数据库 分布式数据库
一条SQL管理向量全生命周期,让AI应用开发更简单
本文探讨了AI应用开发中向量数据管理的挑战,介绍了PolarDB IMCI通过在数据库内核中集成向量索引与Embedding能力,实现向量全生命周期管理的创新方案。该方案有效解决了技术栈分裂、数据孤岛和运维复杂等痛点,提供了一体化、高性能、支持事务与实时检索的向量数据库服务,极大降低了AI应用的开发与维护门槛。
413 26
一条SQL管理向量全生命周期,让AI应用开发更简单
|
9月前
|
人工智能 缓存 自然语言处理
AI 编程如何在团队中真正落地?
如果你是技术负责人、团队推动者或希望在团队中引入 AI 编程工具的工程师,这篇文章将为你提供一条可借鉴、可落地、可优化的路径。
1573 24
AI 编程如何在团队中真正落地?
|
9月前
|
监控 Java 测试技术
OOM排查之路:一次曲折的线上故障复盘
本文分享了在整合Paimon数据湖与RocksDB过程中,因内存溢出(OOM)引发的三次线上故障排查过程。通过SDK进行数据读写时,系统连续出现线程数突增、内存泄漏等问题,排查过程涉及堆内与堆外内存分析、JNI内存泄漏定位及架构优化。最终通过调整bucket数量、优化JVM参数及采用Flink写入Paimon,成功解决问题。文中详述了使用MAT、NMT、Arthas、async-profiler等工具的实战经验,为使用类似技术栈的开发者提供参考。
1101 17
OOM排查之路:一次曲折的线上故障复盘
|
设计模式 中间件 Java
校招阿里这三年,聊点非技术的
作者总结了在阿里的三年时间中所收获的宝贵经验和成长感悟。
|
人工智能 算法 IDE
大厂笔试刷题小妙招
在AI技术飞速发展的今天,程序员如何借助AI突破职业瓶颈?阿里云推出的通义灵码插件提供了答案。这款智能编码助手已全面升级,支持qwen 2.5、qwen 2.5-max及qwq-plus模型,在代码生成与算法解题能力上表现卓越。它能为开发者提供详细的解题思路和完整的代码细节,让每个IDE窗口成为大厂笔试的通关秘籍。无论是暑期实习还是春招,通义灵码都能帮助解决项目问题,提升笔试能力,助力获取大厂offer。
大厂笔试刷题小妙招
|
11月前
|
存储 消息中间件 前端开发
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
校园圈子系统校园论坛小程序采用uni-app前端框架,支持多端运行,结合PHP后端(如ThinkPHP/Laravel),实现用户认证、社交关系管理、动态发布与实时聊天功能。前端通过组件化开发和uni.request与后端交互,后端提供RESTful API处理业务逻辑并存储数据于MySQL。同时引入Redis缓存热点数据,RabbitMQ处理异步任务,优化系统性能。核心功能包括JWT身份验证、好友系统、WebSocket实时聊天及活动管理,确保高效稳定的用户体验。
577 5
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践