附录:专属网关超时时间设置说明

合理设置网关超时时间是保障专属网关稳定性和提升用户体验的关键。不当的设置可能导致连接被意外重置、请求堆积甚至服务雪崩。本文将介绍专属网关中两种核心的超时类型——“空闲连接超时”与“请求超时”,提供涵盖 Java、Go 及阿里云 EAS SDK 的客户端配置建议与代码示例,并分析常见超时问题的排查方法,旨在帮助您构建更稳定、高效的服务。

背景信息

为什么需要设置超时时间?

在分布式系统中,服务之间的调用链路可能很长,任意环节的延迟或故障都可能导致整个请求链的卡顿甚至崩溃。超时机制正是为了解决这类问题而设计,通过设置合理的超时时间,可以:

  • 防止资源耗尽:避免客户端或网关长时间等待无响应的后端服务,导致资源(如连接、线程)被占用,最终引起系统雪崩。

  • 提升用户体验:快速失败优于长时间无响应,让用户能及时收到错误反馈并选择重试或采取其他措施。

  • 保障系统稳定性:及时释放资源,防止慢请求或故障服务耗尽系统资源,从而保护整体系统的稳定性。

设置空闲连接超时时间

空闲连接超时(Idle Connection Timeout)是指在一段时间内没有数据传输的连接会被关闭。这对于管理连接池、释放不活跃资源至关重要。

image

专属网关的配置

专属网关已为您配置了以下固定的空闲连接超时时间,您无需修改:

  • 网关作为服务端(面向客户端):固定为600。客户端与网关的连接若空闲超过此时间,将被网关关闭。

  • 网关作为客户端(面向模型服务):固定为30。网关与后端模型服务的连接若空闲超过此时间,将被网关关闭。

客户端配置建议

强烈建议:客户端的空闲连接超时应小于600秒。

这能确保由客户端主动管理和关闭连接,避免在网关侧关闭连接后,客户端因不知情而使用失效连接带来问题。 即保持客户端的空闲连接超时时间小于专属网关的空闲连接超时时间,从而让客户端来决定连接关闭时机

配置示例

以下是针对不同编程语言管理或设置客户端空闲连接超时时间的示例。

重要

示例仅作为参数设置的参考说明,不能直接应用于业务系统。参数设置需充分考虑业务系统的流量和负载情况,以及使用的客户端版本特性等。

Java

如果使用Apache HttpClient 4.x:Apache HttpClient的连接管理器通过周期性地调用 closeIdleConnections() 方法来管理空闲连接。您需要启动一个单独的线程来执行此操作。

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.client.config.RequestConfig;
import java.util.concurrent.TimeUnit;

public class HttpClientIdleTimeout {
    public static void main(String[] args) throws InterruptedException {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setDefaultMaxPerRoute(20); // 举例值:最大路由连接数
        cm.setMaxTotal(100); // 举例值:最大总连接数

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000) // 举例值:连接超时 5秒
                .setSocketTimeout(10000) // 举例值:读取数据超时 10秒
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            // 启动一个后台线程,每隔一段时间清理空闲连接
            Thread cleanerThread = new Thread(() -> {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        Thread.sleep(5000); // 举例值:每5秒检查一次
                        // 关闭空闲时间超过 500 秒的连接 (小于网关空闲超时时间600秒)
                        cm.closeIdleConnections(500, TimeUnit.SECONDS);
                        // 关闭过期连接(例如被服务端关闭的连接)
                        cm.closeExpiredConnections();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 重新设置中断状态
                }
            });
            cleanerThread.setDaemon(true); // 设置为守护线程,随主线程退出而退出
            cleanerThread.start();

            // 执行HTTP请求...
            // 例如:httpClient.execute(new HttpGet("http://your-gateway-url"));

            // 模拟程序运行一段时间
            Thread.sleep(60000); // 举例值:运行1分钟

            // 停止清理线程 (在实际应用中,您需要在应用关闭时优雅地停止它)
            cleanerThread.interrupt();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Go

Go语言的net/http库通过Transport结构体提供了对连接池的细粒度控制。请确保IdleConnTimeout小于600

package main

import (
    "net/http"
    "time"
)

func main() {
    // 创建一个自定义的Transport
    tr := &http.Transport{
        MaxIdleConns:        100,              // 最大空闲连接数
        IdleConnTimeout:     500 * time.Second, // 空闲连接超时时间,例如500秒 (小于600秒)
        DisableKeepAlives:   false,            // 启用Keep-Alive
    }

    client := &http.Client{
        Transport: tr,
        Timeout:   600 * time.Second, // 整个请求的超时时间,应大于IdleConnTimeout
    }

    // 发送HTTP请求
    // resp, err := client.Get("http://your-gateway-url")
    // if err != nil {
    // 	// 处理错误
    // }
    // defer resp.Body.Close()
}

EAS SDK(Java)

对于阿里云EAS SDK,您可以在客户端设置是否打开清理空闲连接、以及设置空闲连接超时时间。

import com.aliyun.predict.client.PredictClient;
import com.aliyun.predict.client.HttpConfig;
import com.aliyun.predict.proto.TFRequest; // 假设使用TensorFlow请求

public class EasSdkTimeoutJava {
    public static void main(String[] args) {
        // 1. 全局客户端配置
        HttpConfig httpConfig = new HttpConfig();

        PredictClient client = new PredictClient(httpConfig);
        client.setEndpoint("your-eas-service-endpoint");
        client.setModelName("your-model-name");
        client.setCleanupInterval(1); // 打开清理空闲连接开关
        client.setIdleTimeout(500) // 设置空闲连接超时时间,小于网关的空闲连接超时时间600秒
        // client.setToken("your-token"); // 如果需要认证
        ...
    }
}

设置请求超时时间

请求超时(Response Timeout)是指从TCP连接建立、发送请求到接收完整响应的整个过程允许的最大时间,是客户端最常用的超时设置。

image

专属网关的配置

  • 默认值10分钟(600秒)

  • 自定义设置: 您可以在服务配置文件中,通过设置metadata.rpc.keepalive字段来为特定服务自定义网关的请求超时时间。可根据其业务逻辑和预期的处理时间来设置合理的超时时间。网关会读取此值作为该请求的超时时间。配置方法请参考3.metadata参数说明

客户端配置建议

通用建议客户端超时 = 服务端超时 + 少量缓冲时间(例如1-5秒)

  • 缓冲时间用于覆盖网络延迟和服务端处理时间的轻微波动,避免因服务端仍在处理请求而客户端已超时产生的虚假超时错误

  • 请求超时设置应根据实际业务需求进行调整,以上建议仅供参考,不作为强制性指导。在实际生产环境中,需要综合考虑容错性、即时性以及网络抖动等因素,找到适合业务场景的平衡点。

配置示例

以下是针对不同编程语言的客户端请求超时配置示例。

重要

示例仅作为参数设置的参考说明,不能直接应用于业务系统。参数设置需充分考虑业务系统的流量和负载情况,以及使用的客户端版本特性等。

Java

使用 Apache HttpClient 4.x

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class ApacheHttpClientTimeout {
    public static void main(String[] args) {
        // 建议客户端请求超时略大于等于网关请求超时时间(如果是默认的600秒),这里可以是610秒
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000) // 举例值:连接超时,单位毫秒
                .setSocketTimeout(610000) // 数据传输超时 (读取超时),单位毫秒 (610秒)
                .build();
    }
}

Go

Go语言的net/http库提供了多种设置请求超时的方式,最常见的是在http.Client上设置Timeout属性,或者使用context.WithTimeout为单个请求设置超时。

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    // 建议客户端请求超时略大于等于网关请求超时时间(如果是默认的600秒),这里可以是610秒
    client := &http.Client{
        Timeout: 610 * time.Second, // 整个请求的超时时间
    }

    req, err := http.NewRequest("GET", "http://your-gateway-url", nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    // 也可以为单个请求设置更短的超时
    ctx, cancel := context.WithTimeout(req.Context(), 610*time.Second) // 610秒
    defer cancel()
    req = req.WithContext(ctx)

    resp, err := client.Do(req)
    if err != nil {
            fmt.Println("Error sending request:", err)
            // 检查是否是超时错误
            if t, ok := err.(interface{ Timeout() bool }); ok && t.Timeout() {
                    fmt.Println("Request timed out!")
            }
            return
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }
    fmt.Printf("Response Status: %s\n", resp.Status)
    fmt.Printf("Response Body: %s\n", body)
}

EAS SDK(Java)

对于阿里云EAS SDK ,您可以在客户端全局配置或请求级别配置中分别设置连接超时和读取超时。

import com.aliyun.predict.client.PredictClient;
import com.aliyun.predict.client.HttpConfig;
import com.aliyun.predict.proto.TFRequest; // 假设使用TensorFlow请求

public class EasSdkTimeoutJava {
    public static void main(String[] args) {
        // 1. 全局客户端配置
        HttpConfig httpConfig = new HttpConfig();
        httpConfig.setConnectionTimeoutMillis(5000); // 连接超时,单位毫秒
        // 建议客户端请求超时略大于等于网关请求超时时间(如果是默认的600秒),这里可以是610秒
        httpConfig.setReadTimeoutMillis(610000);    // 读取超时,单位毫秒
        ...
    }
}

常见问题

空闲连接超时设置不当

场景一:客户端空闲连接超时时间 > 专属网关作为服务端空闲连接超时时间(即客户端空闲连接超时时间 > 600秒)

  • 问题描述:客户端认为连接仍然有效,但专属网关由于连接空闲时间过长,已主动关闭了该连接。当客户端再次尝试使用此连接发送请求时,会发现连接已断开。

  • 客户端常见报错

    • Connection reset by peer

    • Broken pipe

    • java.net.SocketException: Connection reset(Java)

    • requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))(Python requests)

    • read: connection reset by peer(Go)

    • 客户端可能会收到HTTP 503 Service Unavailable错误,这通常发生在客户端尝试重用已关闭的连接,而网关无法及时建立新连接或将请求转发到后端服务。

场景二:专属网关作为客户端空闲连接超时时间 > 模型服务空闲连接超时时间(即模型服务空闲连接超时时间 < 30秒)

  • 问题描述:专属网关认为连接仍然有效,但后端模型服务由于连接空闲时间过长,已主动关闭了该连接。当网关再次尝试使用此连接将请求转发给模型服务时,会发现连接已断开。

  • 专属网关侧可能报错:网关日志中会出现类似上述客户端的连接重置错误。

  • 客户端侧可能报错:客户端通常会收到专属网关返回的HTTP 503错误码,表明网关无法将请求转发到后端服务。

请求超时设置不当

场景一:客户端请求超时时间设置过短 (小于专属网关请求超时时间)

  • 问题描述:客户端设置的请求超时时间过短。这会导致客户端在请求尚未获得完整响应时就主动中断,从而产生大量的虚假超时错误,影响业务的正常运行和用户体验。

    重要

    在某些场景下,客户端为实现快速失败,会故意将客户端请求超时时间设置的较短,短于服务端请求超时时间。因此,再次强调,客户端请求超时时间的设置值仅供参考,不同场景下可根据实际需求灵活调整,并非一定要大于服务端请求超时时间。本文提供的建议主要针对常规场景,请根据具体业务情况酌情应用。

  • 客户端常见报错

    客户端会频繁抛出其自身的超时异常,例如:

    • java.net.http.HttpTimeoutException(Java Http Client

    • java.net.SocketTimeoutException: Read timed out(Java Apache HttpClient

    • requests.exceptions.ReadTimeout(Python requests

    • context deadline exceeded(Go context.WithTimeoutClient.Timeout触发)

场景二:专属网关请求超时时间小于模型服务实际处理时间

  • 问题描述:专属网关为转发请求到模型服务设置的超时时间(默认10分钟或自定义值),短于模型服务实际处理该请求所需的时间。从而导致网关在模型服务完成处理之前就中断等待,并向客户端返回超时错误。

  • 客户端收到错误:客户端通常会收到来自专属网关的HTTP 504 Gateway Timeout错误,这表明网关在等待后端服务响应时发生了超时。可能是HTTP 502 Bad GatewayHTTP 500 Internal Server Error,具体取决于模型服务如何处理其内部超时并向网关返回错误。

  • 模型服务侧状态:模型服务可能仍在继续处理该请求,其日志中不会有超时错误,但会发现请求被中断(如果网关在超时后关闭了到模型服务的连接)。

  • 排查建议

    当出现上述错误时,建议结合客户端和网关的日志进行排查:

    • 检查客户端日志:查看客户端抛出的具体异常类型和堆栈信息。

    • 检查专属网关日志:查看网关的请求日志,尤其是与该请求相关的错误码和转发状态。

    • 检查模型服务日志:如果网关返回5xx错误码,请进一步检查模型服务的日志,确认是否存在内部错误或处理时间过长导致的超时。