Skip to content

HTTP 超时配置

概述

在微服务架构中,网关作为所有外部请求的入口点,承担着路由转发、负载均衡等重要职责。HTTP 超时配置是网关稳定性的关键因素,它能有效防止因下游服务响应缓慢而导致的资源耗尽和服务雪崩。

Spring Cloud Gateway 提供了灵活的超时配置机制,支持全局超时配置和针对特定路由的超时配置,让开发者能够根据不同业务场景的需求进行精细化的超时控制。

HTTP 超时配置包括连接超时(connect-timeout)和响应超时(response-timeout),两者协同工作来保障网关的稳定性和可用性。

业务场景分析

为什么需要超时配置?

在实际业务中,超时配置解决以下问题:

  1. 防止资源耗尽:避免因慢服务占用过多连接池资源
  2. 提升用户体验:快速失败,避免用户长时间等待
  3. 系统稳定性:防止服务雪崩效应
  4. 容错处理:配合断路器实现优雅降级

全局超时配置

全局超时配置适用于所有路由,作为默认的超时策略。

配置说明

  • connect-timeout:连接超时时间,必须以毫秒为单位指定
  • response-timeout:响应超时时间,必须以 java.time.Duration 格式指定

配置示例

yaml
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000 # 连接超时:1秒
        response-timeout: 5s # 响应超时:5秒
kotlin
@Configuration
class GatewayConfig {

    /**
     * 全局 HTTP 客户端超时配置
     * 通过配置 HttpClient 实现全局超时设置
     */
    @Bean
    fun httpClient(): HttpClient {
        return HttpClient.create()
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) // 连接超时:1秒
            .responseTimeout(Duration.ofSeconds(5))             // 响应超时:5秒
            .doOnConnected { conn ->
                conn.addHandlerLast(ReadTimeoutHandler(5))      // 读取超时:5秒
                    .addHandlerLast(WriteTimeoutHandler(5))     // 写入超时:5秒
            }
    }

    /**
     * 自定义网关过滤器工厂,用于超时处理
     */
    @Bean
    fun reactorNettyClientConfiguration(): ReactorNettyHttpClientFactory {
        return ReactorNettyHttpClientFactory { httpClient() }
    }
}

全局配置建议根据你的业务特点来设置,通常连接超时设置为 1-3 秒,响应超时设置为 5-30 秒。

针对路由的超时配置

对于不同的业务场景,可能需要不同的超时策略。例如,文件上传接口需要更长的超时时间,而健康检查接口需要更短的超时时间。

配置说明

在路由级别的超时配置中:

  • connect-timeout:连接超时时间,以毫秒为单位
  • response-timeout:响应超时时间,以毫秒为单位

注意路由级别的 `response-timeout` 单位是毫秒,而全局配置的单位是 Duration 格式。

通过配置文件实现

yaml
spring:
  cloud:
    gateway:
      routes:
        # 文件上传路由 - 需要较长超时时间
        - id: file_upload_route
          uri: https://file-service.example.com
          predicates:
            - Path=/api/upload/**
          metadata:
            response-timeout: 30000 # 30秒响应超时
            connect-timeout: 2000 # 2秒连接超时

        # 健康检查路由 - 需要快速响应
        - id: health_check_route
          uri: https://health-service.example.com
          predicates:
            - Path=/health/**
          metadata:
            response-timeout: 1000 # 1秒响应超时
            connect-timeout: 500 # 500毫秒连接超时

        # 支付服务路由 - 中等超时时间
        - id: payment_route
          uri: https://payment-service.example.com
          predicates:
            - Path=/api/payment/**
          metadata:
            response-timeout: 10000 # 10秒响应超时
            connect-timeout: 1000 # 1秒连接超时

通过 Java DSL 实现

kotlin
@Configuration
class RouteConfiguration {

    /**
     * 使用 Kotlin DSL 配置不同业务场景的路由超时
     */
    @Bean
    fun customRouteLocator(routeBuilder: RouteLocatorBuilder): RouteLocator {
        return routeBuilder.routes()
            // 文件上传路由配置
            .route("file_upload_route") { r ->
                r.path("/api/upload/**")
                    .filters { f ->
                        f.addRequestHeader("Service-Type", "file-upload")
                         .addResponseHeader("Cache-Control", "no-cache")
                    }
                    .uri("https://file-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 30000) // 30秒响应超时
                    .metadata(CONNECT_TIMEOUT_ATTR, 2000)   // 2秒连接超时
            }
            // 实时数据查询路由
            .route("realtime_data_route") { r ->
                r.path("/api/realtime/**")
                    .and()
                    .method(HttpMethod.GET)
                    .filters { f ->
                        f.addRequestHeader("Priority", "high")
                         .retry { config ->
                             config.setRetries(2)
                                   .setMethods(HttpMethod.GET)
                         }
                    }
                    .uri("https://realtime-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 5000)  // 5秒响应超时
                    .metadata(CONNECT_TIMEOUT_ATTR, 1000)   // 1秒连接超时
            }
            // 批处理任务路由
            .route("batch_processing_route") { r ->
                r.path("/api/batch/**")
                    .and()
                    .header("Content-Type", "application/json")
                    .filters { f ->
                        f.addRequestHeader("Processing-Mode", "batch")
                         .requestSize(10 * 1024 * 1024) // 限制请求大小为10MB
                    }
                    .uri("https://batch-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 60000) // 60秒响应超时
                    .metadata(CONNECT_TIMEOUT_ATTR, 3000)   // 3秒连接超时
            }
            .build()
    }

    companion object {
        // 导入超时配置常量
        private const val RESPONSE_TIMEOUT_ATTR = "response-timeout"
        private const val CONNECT_TIMEOUT_ATTR = "connect-timeout"
    }
}
java
@Configuration
public class RouteConfiguration {

    /**
     * 使用 Java DSL 配置路由超时
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder) {
        return routeBuilder.routes()
            .route("file_upload_route", r -> {
                return r.path("/api/upload/**")
                    .filters(f -> f.addRequestHeader("Service-Type", "file-upload"))
                    .uri("https://file-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 30000)
                    .metadata(CONNECT_TIMEOUT_ATTR, 2000);
            })
            .route("health_check_route", r -> {
                return r.path("/health/**")
                    .filters(f -> f.addRequestHeader("Check-Type", "health"))
                    .uri("https://health-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 1000)
                    .metadata(CONNECT_TIMEOUT_ATTR, 500);
            })
            .build();
    }
}

高级超时配置技巧

禁用全局超时

当某个路由需要无限制的响应时间时,可以通过设置负值来禁用全局超时配置:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: long_running_task
          uri: https://batch-processing.example.com
          predicates:
            - Path=/api/long-task/**
          metadata:
            response-timeout: -1 # 禁用响应超时

动态超时配置

kotlin
@Component
class DynamicTimeoutFilter : GatewayFilter, Ordered {

    /**
     * 基于请求头动态设置超时时间的过滤器
     */
    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        val request = exchange.request
        val timeoutHeader = request.headers.getFirst("X-Timeout")

        return if (timeoutHeader != null) {
            // 根据请求头设置动态超时
            val timeout = Duration.ofMillis(timeoutHeader.toLong())
            chain.filter(exchange)
                .timeout(timeout)
                .onErrorResume(TimeoutException::class.java) {
                    handleTimeoutError(exchange, it)
                }
        } else {
            chain.filter(exchange)
        }
    }

    /**
     * 超时错误处理
     */
    private fun handleTimeoutError(
        exchange: ServerWebExchange,
        ex: TimeoutException
    ): Mono<Void> {
        val response = exchange.response
        response.statusCode = HttpStatus.REQUEST_TIMEOUT
        response.headers.add("Content-Type", "application/json")

        val errorBody = """
            {
                "error": "Request Timeout",
                "message": "请求超时,请稍后重试",
                "timestamp": "${Instant.now()}"
            }
        """.trimIndent()

        val buffer = response.bufferFactory().wrap(errorBody.toByteArray())
        return response.writeWith(Mono.just(buffer))
    }

    override fun getOrder(): Int = -1
}
kotlin
@Configuration
class TimeoutConfiguration {

    /**
     * 基于业务类型的超时配置映射
     */
    @Bean
    fun businessTimeoutConfig(): Map<String, TimeoutConfig> {
        return mapOf(
            "payment" to TimeoutConfig(connectTimeout = 1000, responseTimeout = 15000),
            "upload" to TimeoutConfig(connectTimeout = 2000, responseTimeout = 60000),
            "query" to TimeoutConfig(connectTimeout = 500, responseTimeout = 3000),
            "report" to TimeoutConfig(connectTimeout = 3000, responseTimeout = 120000)
        )
    }

    /**
     * 业务感知的路由配置
     */
    @Bean
    fun businessAwareRoutes(
        routeBuilder: RouteLocatorBuilder,
        timeoutConfig: Map<String, TimeoutConfig>
    ): RouteLocator {
        return routeBuilder.routes()
            .route("payment_route") { r ->
                val config = timeoutConfig["payment"]!!
                r.path("/api/payment/**")
                    .filters { f -> f.addRequestHeader("Business-Type", "payment") }
                    .uri("https://payment-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, config.responseTimeout)
                    .metadata(CONNECT_TIMEOUT_ATTR, config.connectTimeout)
            }
            .route("upload_route") { r ->
                val config = timeoutConfig["upload"]!!
                r.path("/api/upload/**")
                    .filters { f -> f.addRequestHeader("Business-Type", "upload") }
                    .uri("https://upload-service.example.com")
                    .metadata(RESPONSE_TIMEOUT_ATTR, config.responseTimeout)
                    .metadata(CONNECT_TIMEOUT_ATTR, config.connectTimeout)
            }
            .build()
    }

    data class TimeoutConfig(
        val connectTimeout: Long,
        val responseTimeout: Long
    )

    companion object {
        private const val RESPONSE_TIMEOUT_ATTR = "response-timeout"
        private const val CONNECT_TIMEOUT_ATTR = "connect-timeout"
    }
}

超时监控与告警

超时指标收集

kotlin
@Component
class TimeoutMetricsFilter : GatewayFilter, Ordered {

    private val meterRegistry: MeterRegistry = Metrics.globalRegistry
    private val timeoutCounter = Counter.builder("gateway.timeout.count")
        .description("网关超时请求计数")
        .register(meterRegistry)

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        val startTime = System.currentTimeMillis()

        return chain.filter(exchange)
            .doOnError(TimeoutException::class.java) { ex ->
                // 记录超时指标
                timeoutCounter.increment(
                    Tags.of(
                        Tag.of("route", getRouteId(exchange)),
                        Tag.of("uri", exchange.request.uri.path),
                        Tag.of("method", exchange.request.method.name())
                    )
                )

                // 记录超时时长
                Timer.Sample.start(meterRegistry)
                    .stop(Timer.builder("gateway.timeout.duration")
                        .description("网关超时请求持续时间")
                        .register(meterRegistry))

                log.warn("请求超时: {} {} 耗时: {}ms",
                    exchange.request.method,
                    exchange.request.uri,
                    System.currentTimeMillis() - startTime)
            }
    }

    private fun getRouteId(exchange: ServerWebExchange): String {
        return exchange.getAttribute<Route>(GATEWAY_ROUTE_ATTR)?.id ?: "unknown"
    }

    override fun getOrder(): Int = 0

    companion object {
        private val log = LoggerFactory.getLogger(TimeoutMetricsFilter::class.java)
    }
}

最佳实践

1. 超时时间设置原则

2. 分层超时策略

建议采用分层的超时配置策略:

  1. 全局默认:设置保守的默认值
  2. 业务分类:根据业务特点分类配置
  3. 特殊路由:对特殊需求进行个性化配置

3. 配置示例模板

Details

完整的超时配置示例

yaml
spring:
  cloud:
    gateway:
      # 全局超时配置
      httpclient:
        connect-timeout: 1000 # 全局连接超时:1秒
        response-timeout: 10s # 全局响应超时:10秒

      routes:
        # 健康检查 - 快速响应
        - id: health_route
          uri: lb://health-service
          predicates:
            - Path=/health/**
          metadata:
            response-timeout: 1000
            connect-timeout: 500

        # API查询 - 标准响应
        - id: api_query_route
          uri: lb://api-service
          predicates:
            - Path=/api/query/**
          metadata:
            response-timeout: 5000
            connect-timeout: 1000

        # 文件上传 - 长时间响应
        - id: upload_route
          uri: lb://file-service
          predicates:
            - Path=/api/upload/**
          metadata:
            response-timeout: 60000
            connect-timeout: 3000

        # 报表生成 - 超长响应
        - id: report_route
          uri: lb://report-service
          predicates:
            - Path=/api/report/**
          metadata:
            response-timeout: 120000
            connect-timeout: 5000

        # 实时通信 - 禁用超时
        - id: websocket_route
          uri: lb://websocket-service
          predicates:
            - Path=/ws/**
          metadata:
            response-timeout: -1 # 禁用响应超时

4. 监控告警配置

建议配置以下监控指标:

  • 超时请求数量和比率
  • 平均响应时间趋势
  • 不同路由的超时分布
  • 连接池使用情况

常见问题排查

问题 1:频繁超时告警

症状:大量请求出现超时,响应时间过长

排查步骤

  1. 检查下游服务健康状态
  2. 分析网络延迟情况
  3. 检查连接池配置是否合理
  4. 确认超时设置是否过于严格

解决方案

kotlin
// 调整超时配置并增加重试机制
@Bean
fun resilientRouteLocator(routeBuilder: RouteLocatorBuilder): RouteLocator {
    return routeBuilder.routes()
        .route("resilient_route") { r ->
            r.path("/api/critical/**")
                .filters { f ->
                    f.retry { config ->
                        config.setRetries(3)              // 重试3次
                              .setMethods(HttpMethod.GET)   // 只对GET请求重试
                              .setExceptions(TimeoutException::class.java)
                    }
                    .circuitBreaker { config ->
                        config.setName("critical-service-cb")
                              .setFallbackUri("forward:/fallback/critical")
                    }
                }
                .uri("lb://critical-service")
                .metadata(RESPONSE_TIMEOUT_ATTR, 15000)  // 增加超时时间
                .metadata(CONNECT_TIMEOUT_ATTR, 2000)
        }
        .build()
}

问题 2:连接池耗尽

症状:出现连接池满的错误,新请求无法获取连接

解决方案

kotlin
@Bean
fun optimizedHttpClient(): HttpClient {
    val connectionProvider = ConnectionProvider.builder("custom")
        .maxConnections(200)                    // 增加最大连接数
        .maxIdleTime(Duration.ofSeconds(20))    // 设置空闲超时
        .maxLifeTime(Duration.ofSeconds(60))    // 设置连接生命周期
        .pendingAcquireTimeout(Duration.ofSeconds(5)) // 获取连接超时
        .evictInBackground(Duration.ofSeconds(120))   // 后台清理间隔
        .build()

    return HttpClient.create(connectionProvider)
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
        .responseTimeout(Duration.ofSeconds(10))
        .doOnConnected { conn ->
            conn.addHandlerLast(ReadTimeoutHandler(10))
                .addHandlerLast(WriteTimeoutHandler(10))
        }
}

总结

Spring Cloud Gateway 的 HTTP 超时配置是确保微服务架构稳定性的重要机制。通过合理的全局配置和针对性的路由配置,我们可以:

  • 提升系统稳定性:防止慢服务影响整体性能
  • 优化用户体验:快速失败,避免长时间等待
  • 精细化控制:根据不同业务场景设置合适的超时策略
  • 增强可观测性:通过监控指标及时发现和解决问题

超时配置需要根据实际业务需求和系统性能进行调优,建议在生产环境中通过监控数据持续优化配置参数。

TIP

下一步学习