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

Spring流式REST端点报AsyncRequestNotUsableException求助

流式REST端点出现AsyncRequestNotUsableException和Premature EOF问题排查

我实现了一个基于Spring的流式REST端点,控制器和Service层代码如下,原本运行正常,但流式传输一段时间后,服务器抛出AsyncRequestNotUsableException: Response not usable after async request completion,客户端则报Premature EOF错误。

控制器代码

@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> streamData() {
   Stream<Data> data = service.streamData();

   StreamingResponseBody responseBody = httpResponseOutputStream -> {
            try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream))) {
                data.forEach(d -> {
                    try {
                        writer.write(gson.toJson(d));
                        writer.flush();
                    } catch (IOException exception) {
                        logger.error("exception occurred while writing object to stream", exception);
                    }
                });
            } catch (Exception exception) {
                logger.error("exception occurred while publishing data", exception);
            }
            logger.info("finished streaming records");
        };

   return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(responseBody);
}

Service层代码

public Stream<Data> streamData() {
        jdbcTemplate.setFetchSize(500);
        String q = "SELECT .....";
        return this.jdbcTemplate.queryForStream(q,
                (resultSet, rowNum) -> {
                    Data d = transform(resultSet);

                    return d;
                }
        );
    }

以下是排查思路和解决方案:

  • JDBC Stream资源未正确关闭
    jdbcTemplate.queryForStream返回的Stream绑定了ResultSet和数据库连接,若未正确关闭,迭代中出现异常或提前结束时,会导致连接/ResultSet未释放,进而触发异步请求异常。
    解决:用try-with-resources包裹Stream,确保资源自动回收:

    StreamingResponseBody responseBody = httpResponseOutputStream -> {
        try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream));
             Stream<Data> data = service.streamData()) { // 将Stream放入try-with-resources
            data.forEach(d -> {
                try {
                    writer.write(gson.toJson(d));
                    writer.flush();
                } catch (IOException exception) {
                    logger.error("写入对象到流时出错", exception);
                }
            });
        } catch (Exception exception) {
            logger.error("发布数据时出错", exception);
        }
        logger.info("完成流式记录传输");
    };
    
  • 异步请求超时触发终止
    Spring MVC默认的异步请求超时时间较短,若流式传输耗时超过阈值,容器会主动终止异步请求,标记Response为已完成,后续写入会抛出AsyncRequestNotUsableException。
    解决:调整异步超时配置,比如在application.properties中设置:

    spring.mvc.async.request-timeout=3600000 # 设置为1小时,根据实际业务调整
    

    或者通过配置类全局设置:

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            configurer.setDefaultTimeout(3600000);
        }
    }
    
  • 连接超时导致提前断开
    客户端或反向代理(如Nginx)的连接/读取超时设置过短,长时间无数据传输时会主动断开连接,服务器端写入时发现连接已关闭,触发异常,客户端则收到Premature EOF。
    解决:

    • 调整客户端RestTemplate的超时配置:
      RestTemplate restTemplate = new RestTemplate();
      SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
      factory.setConnectTimeout(30000); // 连接超时30秒
      factory.setReadTimeout(3600000); // 读取超时1小时
      restTemplate.setRequestFactory(factory);
      
    • 若使用反向代理,比如Nginx,调整proxy_read_timeout参数为合适的值。
  • 吞吃异常导致无效写入
    当前代码中在forEach的try-catch仅打印日志,未终止流式处理,某条数据写入异常后,后续写入仍会尝试,但此时Response可能已不可用,最终引发更严重的异常。
    解决:捕获IOException后终止处理,改用Iterator遍历以便主动中断:

    try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream));
         Stream<Data> data = service.streamData()) {
        Iterator<Data> iterator = data.iterator();
        while (iterator.hasNext()) {
            Data d = iterator.next();
            try {
                writer.write(gson.toJson(d));
                writer.flush();
            } catch (IOException e) {
                logger.error("写入数据失败", e);
                throw new RuntimeException(e); // 抛出异常终止处理,避免无效写入
            }
        }
    } catch (Exception e) {
        logger.error("流式传输失败", e);
    }
    
  • 数据库连接池配置问题
    连接池的maxLifetime等参数设置过短,会导致流式处理过程中数据库连接被提前回收,ResultSet关闭后引发Stream处理异常,进而影响异步请求。
    解决:检查连接池配置(如HikariCP的maxLifetime、connectionTimeout),确保连接在流式传输期间不会被回收。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 15:03:20