Featured image of post 业务开发问题 代码篇(HTTP调用)

业务开发问题 代码篇(HTTP调用)

对于 HTTP 调用,虽然应用层走的是 HTTP 协议,但网络层面始终是 TCP/IP 协议。 TCP/IP 是面向连接的协议,在传输数据之前需要建立连接。几乎所有的网络框架都会提供 这么两个超时参数:

连接超时参数 ConnectTimeout,让用户配置建连阶段的最长等待时间;

读取超时参数 ReadTimeout,用来控制从 Socket 上读取数据的最长等待时间。

连接超时配置得特别长,比如 60 秒。一般来说,TCP 三次握手建立连接需要的时间非 常短,通常在毫秒级最多到秒级,不可能需要十几秒甚至几十秒。如果很久都无法建 连,很可能是网络或防火墙配置的问题。这种情况下,如果几秒连接不上,那么可能永 远也连接不上。因此,设置特别长的连接超时意义不大,将其配置得短一些(比如 1~5 秒)即可。如果是纯内网调用的话,这个参数可以设置得更短,在下游服务离线无法连 接的时候,可以快速失败。

误区一:Read timed out了,以为服务端不会再往下执行了,实际上服务端在五秒后依然执行完毕了。

接下来展示一个小代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RestController
@RequestMapping("clientreadtimeout")
@Slf4j
public class HttpDemo01 {

    public String getResponse(String path , int connectTime , int readTime) throws Exception {
        URL url = new URL("http://localhost:8001/clientreadtimeout"+path);
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        con.setConnectTimeout(connectTime);
        con.setReadTimeout(readTime);
        return con.getResponseMessage();
    }

    @GetMapping("/client")
    public String client() throws Exception {
        // 连接一秒,读取内容两秒 ,在请求的服务端设置5秒
        return getResponse("/service" , 1000 , 2000);
    }

    @GetMapping("/service")
    public String service(){
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("服务端处理请求完毕");
        return "请求成功";
    }
}

上面模拟了客户端访问服务端的情况,并且在服务端的处理时长设置成了5秒,结局显而易见

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
java.net.SocketTimeoutException: Read timed out
	at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:288) ~[na:na]
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:314) ~[na:na]
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:355) ~[na:na]
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:808) ~[na:na]
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966) ~[na:na]
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244) ~[na:na]
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284) ~[na:na]
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:826) ~[na:na]

	..................

服务端处理请求完毕

误区二:认为读取超时只是 Socket 网络层面的概念,是数据传输的最长耗时,故将其 配置得非常短,比如 100 毫秒。

其实,发生了读取超时,网络层面无法区分是服务端没有把数据返回给客户端,还是数据在 网络上耗时较久或丢包。 但,因为 TCP 是先建立连接后传输数据,对于网络情况不是特别糟糕的服务调用,通常可 以认为出现连接超时是网络问题或服务不在线,而出现读取超时是服务处理超时。确切地 说,读取超时指的是,向 Socket 写入数据后,我们等到 Socket 返回数据的超时时间,其 中包含的时间或者说绝大部分的时间,是服务端处理业务逻辑的时间。

误区三:认为超时时间越长任务接口成功率就越高,将读取超时参数配置得太长。

进行 HTTP 请求一般是需要获得结果的,属于同步调用。如果超时时间很长,在等待服务 端返回数据的同时,客户端线程(通常是 Tomcat 线程)也在等待,当下游服务出现大量 超时的时候,程序可能也会受到拖累创建大量线程,最终崩溃。 对定时任务或异步任务来说,读取超时配置得长些问题不大。但面向用户响应的请求或是微 服务短平快的同步接口调用,并发量一般较大,我们应该设置一个较短的读取超时时间,以 防止被下游服务拖慢,通常不会设置超过 30 秒的读取超时。 你可能会说,如果把读取超时设置为 2 秒,服务端接口需要 3 秒,岂不是永远都拿不到执 行结果了?的确是这样,因此设置读取超时一定要根据实际情况,过长可能会让下游抖动影响到自己,过短又可能影响成功率。甚至,有些时候我们还要根据下游服务的 SLA,为不 同的服务端接口设置不同的客户端读取超时。

使用 Hugo 构建
主题 StackJimmy 设计

发布了 16 篇文章 | 共 31507 字