找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4716

积分

0

好友

625

主题
发表于 1 小时前 | 查看: 3| 回复: 0

如果系统突然卡住了,我们要如何排查呢?如果没有一个逻辑清晰的排查思路,面对这类“系统阻塞”问题,往往会手忙脚乱。

其实,阻塞问题就像人体发烧,只是表面症状,背后可能是网络、后端(数据库等中间件、外部接口等等)根本病因。

本文结合实际分析案例,一步步拆解排查流程,帮助大家找到阻塞的根源。

1. 系统阻塞常见的原因

从前端到后端,梳理一下可能导致系统阻塞的常见原因:

  • 前端无限制的瞬时并发请求
  • 大文件上传、导出
  • 线程池耗尽
  • 内存泄漏/溢出
  • 代码逻辑错误,比如死循环
  • 数据库连接池占满
  • 数据库慢查询(如索引不生效、在刷脏页等)
  • 下游接口无响应,但是又没设置超时时间
  • 缓存、消息队列故障

1.1 前端无限制的瞬时并发请求

如果用户短时间内发起上百上千个相同接口请求,导致后端线程池被瞬间占满的话,系统就会卡住。

因此,我们在设计系统的时候,一般要求做一下限流控制。

1.2 大文件上传、导出

大文件导出(如导出 10 万行以上的 Excel数据)的阻塞多发生在 “数据处理” 和 “文件生成” 阶段。

  • 比如,一次性加载全量数据,内存直接 “撑爆”
//反例代码:一次性查询100万条数据
List<User> userList = userMapper.selectAll(); // 假设100万条,每条占1KB,共1GB

若此时有 2 个导出请求,直接触发 OOM;即使没 OOM,大量对象创建会导致 Young GC 频繁(每秒几次),系统响应延迟从 100ms 增至 1-2 秒。

  • 再比如,POI 是 Java 处理 Excel 的常用库,之前在原来的公司,就遇到个POI导致的full GC问题,最后系统响应变慢

POI 生成.xlsx文件时,会把整个文档结构保存在内存,10 万行数据可能占用 500MB-1GB 内存,且对象回收不及时,最后可能触发full GC的问题。

1.3 线程池耗尽

比如,Tomcat 默认最大线程数为 200,若接口平均处理时间从 100ms 增至 5s,每秒只能处理 40 个请求,当并发超过 40 时就会排队,超过 200 则直接拒绝。

1.4 内存泄漏/溢出

未关闭的 IO 流、静态集合无限制存储数据,会导致 JVM 内存持续增长,最终触发 GC 频繁(STW 时间变长)甚至 OOM,系统表现为 “间歇性无响应”

1.5 代码逻辑错误,比如死循环

假设有一个接口,每次请求都会启动一个线程执行一段死循环代码:

当用户多次调用这个接口后:

  • 系统会创建大量线程,每个线程都陷入死循环,疯狂占用 CPU
  • 很快 Tomcat 线程池被占满,新请求无法处理
  • 此时调用其他接口会发现:要么超时,要么完全无响应 —— 系统阻塞了

这就是死循环导致系统阻塞的过程:无限制消耗 CPU 和线程资源,最终让整个系统无法处理新请求。

1.6 数据库连接池占满

数据库连接池占满是导致系统阻塞的常见原因之一。当连接池中的连接被耗尽且无法及时释放时,新的数据库操作请求会排队等待空闲连接,最终导致系统响应缓慢或无响应。

比如这个简单例子:

  • 假设数据库连接池的最大连接数配置为 10
  • 当用户多次调用/leak-connection接口(比如调用 10 次),连接池中的 10 个连接会被全部占用且不释放
  • 此时调用/normal-query接口时,会发现请求一直处于等待状态
  • 因为新的请求无法从连接池中获取到空闲连接,只能排队等待,最终导致接口超时

1.7 数据库慢查询

慢查询是指执行时间过长的 SQL(如未加索引的全表扫描、复杂关联查询),会长期占用数据库连接和 CPU。

示例代码:

@GetMapping("/slow-query")
public String slowQuery() {
    // 无索引的大表全表扫描(假设user表有1000万行)
    List<Map<String, Object>> result = jdbcTemplate.queryForList(
        "SELECT * FROM user WHERE create_time > '2023-01-01'"
    );
    return "查询到" + result.size() + "条数据";
}

阻塞过程:

  • 这条 SQL 执行需要 30 秒(无索引导致全表扫描)
  • 执行期间,该请求占用的数据库连接被 “锁住”,无法释放
  • 若同时有 5 个这样的请求,连接池的 5 个连接会被占用 30 秒
  • 其他需要数据库操作的请求必须排队等待,表现为 “接口超时”(即使连接池没满,也会因等待慢查询释放连接而阻塞)

1.8 下游接口无响应,但是又没设置超时时间

这个阻塞主要表现如下:

  • 假设下游接口 http://downstream-service/unresponsive 已崩溃,无法响应任何请求
  • 当用户调用 /call-downstream 时,RestTemplate会发起请求并一直等待(无超时设置)
  • 每个/call-downstream请求会占用一个 Tomcat 线程,且永远不会释放
  • 当调用次数达到 Tomcat 最大线程数(默认 200),线程池被完全占满
  • 此时调用/health接口会发现:请求无响应或超时 —— 系统已阻塞

1.9 缓存、消息队列故障

缓存和消息队列等中间件故障,导致系统阻塞的原理与下游接口超时类似。

当依赖的中间件出现问题,而系统未做防护处理时,会导致线程长期占用,最终引发整体阻塞。

我们以缓存故障(以 Redis 为例),阻塞过程:

  1. 假设 Redis 服务崩溃,所有连接请求都会卡住
  2. 用户调用/get-user时,redisTemplate.get()会无限等待 Redis 响应(默认无超时)
  3. 每个请求占用一个 Tomcat 线程,且不会释放
  4. 线程池被占满后,/ping等正常接口也无法响应 —— 系统阻塞

缓存客户端(如 RedisTemplate)未设置连接超时,且未实现降级逻辑(如缓存不可用时直接返回默认值),导致线程被永久占用。

2. 一次系统阻塞的真实排查过程

上个月我们的系统出现了阻塞的生产问题。下面简单分享一下我的排查思路和过程:

当时收到系统监控告警,核心问题是好多请求都超时了,没有处理成功

  1. 检查请求量:通过日志平台查看请求量,发现并不大,没有瞬时高并发的场景。排除了高并发或第三方攻击导致的可能性。
  2. 排查大文件操作:发现主要调用的是一个排班接口,量并不特别大,也没有处理大文件的接口。排除大文件上传、导出的原因。
  3. 观察JVM状态:查看JVM监控面板,没有发现内存泄露或OOM的迹象。排除内存相关问题。
  4. 检查数据库层面:查看慢SQL监控面板,没有发现大量慢SQL,数据库连接池状态也正常。排除数据库慢查询等可能。
  5. 确认中间件状态:检查Redis、ES、RocketMQ等中间件,发现都能正常响应。排除中间件自身故障的影响。
  6. 聚焦下游调用:通过日志分析,发现调用下游的排版接口时,一直没返回,大量请求被挂起。因此初步怀疑,可能是没有设置超时时间或超时时间设置不合理导致的。

最终跟踪代码和日志,确实发现了问题根源:居然没有设置超时时间! 下游接口出故障后,由于原系统没有对这个接口设置超时时间,导致Tomcat线程池被占满。

问题代码示例:

@Autowired
private RestTemplate restTemplate;

JSONObject result = restTemplate.postForEntity(url, data, JSONObject.class).getBody();

@Configuration
public class RestemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

这个RestTemplate作为远程调用模板,需要设置连接和读取超时时间,正确写法如下:

    @Bean
    public RestTemplate restTemplate() {
        // 创建请求工厂并设置超时参数
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

        // 连接超时时间:3秒(单位:毫秒)
        requestFactory.setConnectTimeout(3000);
        // 读取超时时间:5秒(单位:毫秒)这个时间,大家根据实际业务调整
        requestFactory.setReadTimeout(5000);

        // 使用配置好的工厂创建RestTemplate
        return new RestTemplate(requestFactory);
    }

总结

系统阻塞问题的排查,需要有一个清晰的、从外到内的排查路径。通常可以从监控告警入手,依次排除网络、前端并发、应用自身(线程、内存、代码)、数据库等中间件、以及下游依赖等各个环节的问题。本文列举的九种常见原因和实战排查案例,希望能为大家提供一个有效的参考框架。在实际工作中,结合完善的监控体系和日志记录,能更快地定位并解决问题。更多类似的Java实战排查经验,欢迎在云栈社区的技术论坛中交流探讨。




上一篇:JetBrains IDE 深度集成 OpenAI Codex,AI 编程体验全面升级
下一篇:SpringBoot 生产环境必改的10项默认配置与优化方案
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-28 09:48 , Processed in 0.639389 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表