StackTraces
Spring Boot已验证

RestTemplate 报 SocketTimeoutException

@debug_master32 天前更新阅读 6 min00

Read timed out 不是连不上,是连上了、请求也发出去了,但响应迟迟读不回来。RestTemplate 自己不定超时,把它交给底层工厂;默认工厂的超时是 0,也就是「无穷等」,一个卡住的下游接口能把调用线程一直挂死,量一大线程池就被拖垮。

Issue & Env(案发现场与环境)

  • 现象:调用外部 HTTP 接口的代码偶尔或高并发时抛超时,应用整体变慢,甚至所有接口都开始排队。
  • 环境:Spring Boot 2.x / 3.x,RestTemplate,默认使用 SimpleClientHttpRequestFactory(底层是 JDK 的 HttpURLConnection)。

StackTrace(堆栈日志)

标准超时异常:

java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
    at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1593)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1490)
    at org.springframework.http.client.SimpleClientHttpRequestFactory$SimpleBufferingClientHttpRequest.executeInternal(SimpleClientHttpRequestFactory.java:72)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:741)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:694)
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:325)
    at com.example.PaymentClient.callDownstream(PaymentClient.java:57)

它和连接超时是两码事。连接超时是 TCP 握手阶段失败,报的是:

java.net.ConnectException: Connection timed out

Read timed out 说明连接已建立,但等响应数据的阶段超时了。

Reproduce(复现代码)

下游接口故意睡很久,客户端用默认 RestTemplate 调它:

// 下游:睡 30 秒再返回
@GetMapping("/slow")
public String slow() throws InterruptedException {
    Thread.sleep(30000);
    return "ok";
}
// 客户端:new RestTemplate() 没配任何超时
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject("http://localhost:8080/slow", String.class);

因为默认没有读超时,这行代码会一直阻塞到下游返回,中途不会自己报 Read timed out。你看到的 Read timed out,通常是在某个地方显式配了超时之后才会出现。

Root Cause(根因分析)

RestTemplate 是一个门面,真正的连接、读写由 ClientHttpRequestFactory 完成。new RestTemplate() 默认用 SimpleClientHttpRequestFactory,它内部是 JDK 的 HttpURLConnection。HttpURLConnection 的 connect 和 read 超时默认都是 0,即永不超时。

这个 0 就是坑。它意味着:只要下游接口不按预期速度返回,调用线程就会死死卡在 getForObject 上,既不报错也不释放。Tomcat 处理请求的线程就这么多,几十个慢调用一叠加,线程池被耗尽,新请求全部排队,最终整站「假死」。日志里往往看不到明显异常,只有线程 dump 或者「Read timed out」这类散落的超时,排查难度极高。

所以要先分清两个超时:connectTimeout 管建立连接,readTimeout 管等响应。两者都要设,缺一个都可能埋雷。

The Fix(解决方案)

Spring Boot 里最干净的做法是用 RestTemplateBuilder:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
            .connectTimeout(Duration.ofSeconds(3))
            .readTimeout(Duration.ofSeconds(5))
            .build();
}

手写工厂也行:

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
RestTemplate restTemplate = new RestTemplate(factory);

生产环境建议换成带连接池的 HttpComponentsClientHttpRequestFactory,避免每次请求都新建连接。超时值不要拍脑袋:readTimeout 要小于上层网关和你自己服务的总超时,否则会发生「你还在等下游,上游已经放弃你」的错位。真遇到慢接口,先查下游为什么慢,而不是一味把超时调大。

References(参考来源)

最后更新于 2026-09-09

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法