背景

我们知道任何的客户端与服务端发起接口请求,都需要等待服务端处理完之后响应,

那么等待时间不能确定什么时候可以响应,但是客户端不可能一直等待中,所以客户端

就要设置一个超时时间,不管是否处理完,过了设置的指定时间就等于是超时。

那么在微服务中如何处理超时的呢?

微服务客户端

在微服务中有两个调用客户端

  1. 网关路由客户端 (Netty Client)
  2. 服务调用客户端 (Feign Client)
    • Feign Client 可以是 Apache HttpClientOK HttpClientJDK HttpClient 或其他

很多人认为 网关的客户端 用的是 Feign ,其实不是的。

  • 网关路由是使用的 org.springframework.cloud.gateway.filter.GlobalFilter 实现的
  • 服务调用是使用 feign.Client 实现的

原理差不多都是使用责任链设计模式

设置超时时间

网关路由

应用超时代码行
  • org.springframework.cloud.gateway.filter.NettyRoutingFilter

    	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    		. . .
    		Duration responseTimeout = getResponseTimeout(route);
    		if (responseTimeout != null) {
    			responseFlux = responseFlux
    					.timeout(responseTimeout, Mono.error(new TimeoutException(
    							"Response took longer than timeout: " + responseTimeout)))
    					.onErrorMap(TimeoutException.class,
    							th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
    									th.getMessage(), th));
    		}
    
    		return responseFlux.then(chain.filter(exchange));
    	}
        private Duration getResponseTimeout(Route route) {
    		Object responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR);
    		Long responseTimeout = null;
    		if (responseTimeoutAttr != null) {
    			if (responseTimeoutAttr instanceof Number) {
    				responseTimeout = ((Number) responseTimeoutAttr).longValue();
    			}
    			else {
    				responseTimeout = Long.valueOf(responseTimeoutAttr.toString());
    			}
    		}
    		return responseTimeout != null ? Duration.ofMillis(responseTimeout)
    				: properties.getResponseTimeout();
    	}
    
配置超时时间
  • org.springframework.cloud.gateway.config.HttpClientProperties

    • connectTimeout 连接超时时间
    • responseTimeout 响应超时时间
  • 配置示例

    spring:
      cloud:
        gateway:
          enabled: true
          httpclient:
            connectTimeout: 1000
            responseTimeout: 5000  
    

服务调用

应用超时代码行
  • org.springframework.cloud.openfeign.FeignClientFactoryBean

       protected void configureUsingProperties(
    			FeignClientProperties.FeignClientConfiguration config,
    			Feign.Builder builder) {
    	    . . .
    		connectTimeoutMillis = config.getConnectTimeout() != null
    				? config.getConnectTimeout() : connectTimeoutMillis;
    		readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout()
    				: readTimeoutMillis;
    		followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects()
    				: followRedirects;
    
    		builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS,
    				readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects));
           . . . 
    	}
    

    FeignName 在初始化的时候已经设置

  • feign.okhttp.OkHttpClient OKhttp 作示例

        ...
        public feign.Response execute(feign.Request input, feign.Request.Options options)
          throws IOException {
        okhttp3.OkHttpClient requestScoped;
        if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
            || delegate.readTimeoutMillis() != options.readTimeoutMillis()
            || delegate.followRedirects() != options.isFollowRedirects()) {
          requestScoped = delegate.newBuilder()
              .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
              .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
              .followRedirects(options.isFollowRedirects())
              .build();
        } else {
          requestScoped = delegate;
        }
        Request request = toOkHttpRequest(input);
        Response response = requestScoped.newCall(request).execute();
        return toFeignResponse(response, input).toBuilder().request(input).build();
      }
    
配置超时时间
  • 配置类 org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration

    • connectTimeout 连接超时时间
    • readTimeout 响应超时时间
  • 配置示例

    feign:
      client:
        config:
          default:   
            connectTimeout: 500
            readTimeout: 3000
    
    • default 是默认应用值,此处可以替代 feign name 这样的话指定该feign 下面的所有方法都是使用该超时时间
  • 如果需要特定方法不同的超时时间的话,需要在feign 方法中添加feign.Request.Options 参数,或者是使用拦截器feign.RequestInterceptor 设置

    @PostMapping("/e1")
    ResponseEntity<String> e1(Options options);
    

Feign 架构图

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐