You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot应用线程阻塞致请求挂起的排查与调优问询

结合你提供的jstack日志和应用场景,核心问题很明确:Jetty工作线程被阻塞的Mqtt同步发布调用占用,导致线程池耗尽,新请求无法处理;同时自定义线程池也处于等待状态,任务队列可能已经堆积。下面逐个解答你的三个问题:

1. 如何确定Spring应用/JVM/Jetty服务器可承载的线程数量?

确定合理的线程数需要结合应用类型、硬件资源和实际压测验证,分几个维度来看:

  • 区分应用类型:你的应用以长等待API(12-60秒)为主,属于IO密集型(大部分时间在等待外部资源,比如Mqtt消息发送、远程调用),这类场景的线程数公式参考:线程数 = CPU核心数 × (1 + 等待时间/CPU处理时间)。AWS t2.medium是2核CPU,假设你的API等待时间是30秒,CPU处理时间是0.1秒,那么线程数可以设置在2×(1+30/0.1)=602左右,但实际要考虑资源上限,不能盲目设置过高。
  • 考虑JVM资源限制:每个线程默认栈大小是1MB左右,250个线程就占用约250MB内存,你的JVM堆是2G,t2.medium总内存4G,内存层面还有余量,但线程数过多会导致CPU上下文切换开销剧增,反而降低性能。
  • Jetty线程池的参考值:Jetty的QueuedThreadPool默认最大线程数是200,你当前线程数到250+说明已经超过默认值,需要结合压测调整。可以先从核心线程数=CPU核心数×2,最大线程数=CPU核心数×10开始测试,再根据监控数据调整。
  • 实际压测验证:用JMeter、Gatling等工具模拟并发请求,监控CPU使用率、线程池活跃数、响应时间、错误率等指标,找到性能拐点(比如CPU使用率超过80%、响应时间急剧上升),此时的并发数就是你的合理承载阈值。
2. 如何调优该应用以避免此类服务不可用的情况?

针对你的场景,核心优化方向是减少Jetty工作线程的阻塞,同时优化线程池配置:

  • 异步化阻塞操作:从jstack日志看到,Jetty工作线程卡在MqttTopic.publish的同步调用上,这是关键瓶颈。把Mqtt消息发布操作放到自定义的threadPoolTaskExecutor中异步执行,不要占用Jetty的工作线程。示例代码:
    @Autowired
    private ThreadPoolTaskExecutor mqttTaskExecutor;
    
    public void publishMqttMessage(String topic, String payload) {
        mqttTaskExecutor.submit(() -> {
            // 原Mqtt同步发布逻辑
            mqttTopic.publish(payload.getBytes());
        });
    }
    
  • 优化Mqtt客户端配置:改用MqttAsyncClient的异步发布API,避免同步阻塞;检查Mqtt的QoS级别,如果不需要消息可靠送达,降低到QoS 0(无需确认),减少等待时间;同时配置Mqtt客户端的超时时间,避免无限等待。
  • 调整线程池参数:
    • Jetty线程池:设置合理的minThreads(核心线程数)、maxThreads(最大线程数)和idleTimeout(空闲线程超时时间),比如minThreads=4、maxThreads=100,避免线程数过度膨胀;
    • 自定义threadPoolTaskExecutor:根据异步任务的数量调整核心/最大线程数,设置合适的队列容量(比如LinkedBlockingQueue设置固定容量),并配置拒绝策略(比如CallerRunsPolicy让调用线程处理,或者自定义返回503错误),防止任务无限堆积。
  • 添加请求超时控制:针对长等待API,在Spring中通过@RequestMapping(timeout = ...)设置请求超时,或者在Jetty配置中设置全局请求超时,避免线程被长时间占用。
  • 监控线程状态:用JMX、Prometheus+Grafana监控线程池的活跃线程数、队列长度、任务拒绝次数,当指标异常时及时告警,提前发现问题。
3. 如何在应用挂起前对API进行限流?

限流的核心是在资源耗尽前拒绝部分请求,保护应用可用,推荐几种适合你场景的方案:

  • 基于线程池的限流:利用线程池的最大线程数+队列容量实现限流,当线程池满、队列也满时,触发拒绝策略,直接返回503 Service Unavailable。这种方式简单有效,适合控制并发请求数。
  • 使用Guava RateLimiter实现请求数限流:针对单个API或者全局设置每秒允许的请求数,比如限制长等待API每秒处理10个请求,避免过多请求占用资源。示例代码:
    private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒10个请求
    
    @GetMapping("/long-wait-api")
    public ResponseEntity<?> longWaitApi() {
        if (!rateLimiter.tryAcquire()) {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁,请稍后再试");
        }
        // 业务逻辑
        return ResponseEntity.ok("success");
    }
    
  • Jetty内置限流Filter:使用Jetty的QoSFilter或者自定义Filter,在请求进入Jetty工作线程前进行限流,比如限制并发连接数或者请求速率。
  • 熔断降级:结合Resilience4j的CircuitBreaker,当Mqtt服务响应慢或者不可用时,触发熔断,直接返回降级响应,避免线程被阻塞在Mqtt调用上。
  • 基于资源指标的动态限流:监控CPU使用率、线程池活跃数等指标,当达到阈值(比如CPU超过70%、线程池活跃数达到80%最大线程数)时,触发限流,动态调整允许的请求数。

内容的提问来源于stack exchange,提问作者Sakshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 09:08:17