Spring Cloud:深入浅出服务调用组件Feign原理、源码分析

Feign 是一个声明式 Web服务客户端,使用它创建一个接口并注解,使得编写 Web服务客户端变得更加容易。

它支持可插拔注解,包括 Feign 注解和 JAX-RS 注解,还支持可插播得编码、解码器。

Cloud 增加了对 Spring MVC 注解的支持,默认使用 httpmessageconverter 的支持。

Cloud 集成了 Ribbon 和 Eureka以及 BalanceLoad,使得在使用 Feign 时支持 Http 客户端的负载均衡。

Feign 自定义的客户端配置不需要使用 @Configuration 配置注解。如果使用了,需要排除在 @ComponentScan 注解扫描之外。否则,它将成为 feign.Encoder、feign.Decoder、feign.Contract等组件的默认实现。如果指定了,可以在 @ComponentScan 中显示排除。

        

二、源码分析

1、 客户端配置、Bean定义加载流程
org.springframework.cloud.openfeign.EnableFeignClients 代码片段
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	// basePackages 的别名
	String[] value() default {};
	
	//@FeignClient 注解组件扫描的基础包路径
	String[] basePackages() default {};
	
	Class<?>[] basePackageClasses() default {};
	
	//指定所有 feign 客户端的自定义配置.
	Class<?>[] defaultConfiguration() default {};
	
	//@FeignClient 注解的客户端类列表,不为空的话,关闭类路径的扫描
	Class<?>[] clients() default {};
}

        @EnableFeignClients 导入客户端注册类。

在这里插入图片描述
        通过图解,我们知道它会在容器期间注册一些 bean的定义。

org.springframework.cloud.openfeign.FeignClientsRegistrar 代码片段
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	//注册默认的配置
	registerDefaultConfiguration(metadata, registry);
	//注册客户端
	registerFeignClients(metadata, registry);
}

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	//忽略部分逻辑代码...
	
	//@EnableFeignClients 无指定客户端类情况下,设置过滤筛选的注解类和获取扫描基础包路径
	if (clients == null || clients.length == 0) {
		scanner.addIncludeFilter(annotationTypeFilter);
		basePackages = getBasePackages(metadata);
	}
	
	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				//加载@FeignClient 注解的属性集
				Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
				//加载客户端名称,其 contextId 可覆盖 value属性
				String name = getClientName(attributes);
				//注册客户端配置
				registerClientConfiguration(registry, name,attributes.get("configuration"));
				//注册客户端实例Bean(重点)
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
}

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	//获取被注解的目标类型
	String className = annotationMetadata.getClassName();
	//生成 FeignClientFactoryBean bean的定义(重点)
	BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
	validate(attributes);
	definition.addPropertyValue("url", getUrl(attributes));
	definition.addPropertyValue("path", getPath(attributes));
	String name = getName(attributes);
	definition.addPropertyValue("name", name);
	String contextId = getContextId(attributes);
	definition.addPropertyValue("contextId", contextId);
	definition.addPropertyValue("type", className);
	definition.addPropertyValue("decode404", attributes.get("decode404"));
	definition.addPropertyValue("fallback", attributes.get("fallback"));
	definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

	String alias = contextId + "FeignClient";
	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

	boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null

	beanDefinition.setPrimary(primary);

	String qualifier = getQualifier(attributes);
	if (StringUtils.hasText(qualifier)) {
		alias = qualifier;
	}
	
	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

        从上述代码可以看到向容器中注册一个 FeignClientFactoryBean 类型的 bean 定义,由它生成最终目标接口的 bean 实例。

        

2、客户端实例加载流程

在这里插入图片描述
        通过图解,我们知道 FeignClientFactoryBean 实现了 FactoryBean、InitializingBean,通过 FactoryBean 获取到自定义的Bean实例。

org.springframework.cloud.openfeign.FeignClientFactoryBean 代码片段
<T> T getTarget() {
	//FeignContext工厂类,能够为每个feign客户端创建一个IOC子容器,并创建相关组件的实例
	FeignContext context = this.applicationContext.getBean(FeignContext.class);	
	
	//创建Builder实例(构造Http API的工厂实例),设置相关组件配置(编码、解码器和拦截器(RequestInterceptor)等)
	Feign.Builder builder = feign(context);
	
	if (!StringUtils.hasText(this.url)) {
		if (!this.name.startsWith("http")) {
			this.url = "http://" + this.name;
		}
		else {
			this.url = this.name;
		}
		//path 不为空,追加到url后面,作为前缀
		this.url += cleanPath();
		//负载均衡
		return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url));
	}
	
	//忽略部分逻辑代码...
}

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
	//加载Feign 负载均衡客户端(LoadBalancerFeignClient)
	Client client = getOptional(context, Client.class);
	if (client != null) {
		builder.client(client);
		//获取(HystrixTargeter)
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
}

        

feign.Feign 代码片段
public Feign build() {
	//创建代理方法处理器的工厂实例
	SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
	    new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
	        logLevel, decode404, closeAfterDecode, propagationPolicy);
	        
    //创建请求方法 转 Rest请求的方法执行(MethodHandler) 解析处理实例
	ParseHandlersByName handlersByName =
	    new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
	        errorDecoder, synchronousMethodHandlerFactory);
	        
	//创建feign 反射,实例化代理成目标接口实例
	return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

        

feign.ReflectiveFeign 代码片段
public <T> T newInstance(Target<T> target) {
	 //获取目标接口方法的执行实例
	 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
	 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
	 List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	 //遍历目标接口方法
	 for (Method method : target.type().getMethods()) {
	   if (method.getDeclaringClass() == Object.class) {
	     continue;
	   } else if (Util.isDefault(method)) {
	     DefaultMethodHandler handler = new DefaultMethodHandler(method);
	     defaultMethodHandlers.add(handler);
	     methodToHandler.put(method, handler);
	   } else {
	   	 //所有方法执行实例,添加到分发器中
	     methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
	   }
	 }
	 //创建目标方法调用执行实例,对目标接口所有方法的执行都会通过该实例发出调用
	 InvocationHandler handler = factory.create(target, methodToHandler);
	 //创建目标接口的代理实例
	 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
	
	 for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
	   defaultMethodHandler.bindTo(proxy);
	 }
	 return proxy;
}
feign.ReflectiveFeign.ParseHandlersByName 代码片段
public Map<String, MethodHandler> apply(Target key) {
  //解析出方法的元数据
  List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
  Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
  for (MethodMetadata md : metadata) {
    BuildTemplateByResolvingArgs buildTemplate;
    //根据传递的参数方式,生成相应的模板构建器
    //该构建器在请求调用期间会解析出请求的模板,
    if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
      buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
    } else if (md.bodyIndex() != null) {
      buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
    } else {
      buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
    }
    //创建目标接口方法的执行实例(SynchronousMethodHandler)
    result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
  }
  return result;
}

        参数扩展点:在解析方法的元数据期间,我们可以自定义方法参数的处理器 (实现 AnnotatedParameterProcessor 接口),指定请求参数的位置。在构建请求模板的时候,可以通过 QueryMapEncoder 实现实例完成参数的解析、转换。

        示例 @SpringQueryMap 注解结合 FieldQueryMapEncoder,完成 Get Http请求的 复合对象传参的解析、转换。

        至此,通过JDK的动态代理机制,返回目标接口的代理实例。
        

3、客户端请求执行过程
feign.ReflectiveFeign.FeignInvocationHandler 代码实例
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //忽略部分代码逻辑

  //通过分发器调用对应的方法执行实例
  return dispatch.get(method).invoke(args);
}
feign.SynchronousMethodHandler 代码片段
@Override
public Object invoke(Object[] argv) throws Throwable {
  //通过模板构建器,创建请求模板实例
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  Options options = findOptions(argv);
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {
      //执行请求和解码
      return executeAndDecode(template, options);
    } catch (RetryableException e) {}
  }
}

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  //忽略部分代码逻辑...
  
  //应用请求拦截器(RequestInterceptor),创建请求实例
  Request request = targetRequest(template);
  Response response;
  try {
    response = client.execute(request, options);
  } catch (IOException e) {
    throw errorExecuting(request, e);
  }
  boolean shouldClose = true;
  try {
    if (Response.class == metadata.returnType()) {
      if (response.body() == null) {
        return response;
      }
      if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
        shouldClose = false;
        return response;
      }
      // Ensure the response body is disconnected
      byte[] bodyData = Util.toByteArray(response.body().asInputStream());
      return response.toBuilder().body(bodyData).build();
    }
    if (response.status() >= 200 && response.status() < 300) {
      if (void.class == metadata.returnType()) {
        return null;
      } else {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      }
    } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
      Object result = decode(response);
      shouldClose = closeAfterDecode;
      return result;
    } else {
      throw errorDecoder.decode(metadata.configKey(), response);
    }
  } catch (IOException e) {
    throw errorReading(request, response, e);
  } finally {
    if (shouldClose) {
      ensureClosed(response.body());
    }
  }
}

        请求扩展点:在构造请求实例期间,可以通过实现 RequestInterceptor 接口,并结合配置文件(可参照FeignClientProperties配置属性)指定客户端实例的请求拦截器。

        示例,可参照 BasicAuthRequestInterceptor

        通过上述请求模板的构建,生成请求实例后,进入请求调用阶段。

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient 代码片段
@Override
public Response execute(Request request, Request.Options options) throws IOException {
	try {
		URI asUri = URI.create(request.url());
		String clientName = asUri.getHost();
		URI uriWithoutHost = cleanUrl(request.url(), clientName);
		FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);

		IClientConfig requestConfig = getClientConfig(options, clientName);
		return lbClient(clientName)
				.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
	}
	catch (ClientException e) {
		throw new RuntimeException(e);
	}
}

        通过 LoadBalancerFeignClient 实例调用 lbClient 方法,通过 CachingSpringLoadBalancerFactory 创建了 FeignLoadBalancer 负载均衡实例。

在这里插入图片描述

org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer 代码片段
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {
	super(lb, clientConfig);
	this.setRetryHandler(RetryHandler.DEFAULT);
	this.clientConfig = clientConfig;
	//获取ribbon的配置,并作为默认的属性
	this.ribbon = RibbonProperties.from(clientConfig);
	RibbonProperties ribbon = this.ribbon;
	this.connectTimeout = ribbon.getConnectTimeout();
	this.readTimeout = ribbon.getReadTimeout();
	this.serverIntrospector = serverIntrospector;
}

@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
	Request.Options options;
	if (configOverride != null) {
		//根据覆盖的配置,获取ribbon配置属性
		RibbonProperties override = RibbonProperties.from(configOverride);
		//若覆盖的属性不存在,则使用ribbon的配置属性
		options = new Request.Options(override.connectTimeout(this.connectTimeout),
				override.readTimeout(this.readTimeout));
	}
	else {
		options = new Request.Options(this.connectTimeout, this.readTimeout);
	}
	Response response = request.client().execute(request.toRequest(), options);
	return new RibbonResponse(request.getUri(), response);
}

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {}
}

         FeignLoadBalancer 实例初始化时,会加载 ribbon 的配置。最后在请求调用期间,若feign配置属性存在,则覆盖ribbon的配置属性。

com.netflix.loadbalancer.reactive.LoadBalancerCommand 代码片段
public Observable<T> submit(final ServerOperation<T> operation) {
    final ExecutionInfoContext context = new ExecutionInfoContext();
    
    final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
    final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();

    // Use the load balancer
    Observable<T> o = 
            (server == null ? selectServer() : Observable.just(server))
            .concatMap(new Func1<Server, Observable<T>>() {
                @Override
                // Called for each server being selected
                public Observable<T> call(Server server) {
  
                    // Called for each attempt and retry
                    Observable<T> o = Observable
                            .just(server)
                            .concatMap(new Func1<Server, Observable<T>>() {
                                    return operation.call(server).doOnEach(new Observer<T>() {
                                    });
                                }
                            });
                    
                    if (maxRetrysSame > 0) 
                        o = o.retry(retryPolicy(maxRetrysSame, true));
                    return o;
                }
            });
        
    if (maxRetrysNext > 0 && server == null) 
        o = o.retry(retryPolicy(maxRetrysNext, false));
    
    return ...
}

private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
        @Override
        public void call(Subscriber<? super Server> next) {
            try {
                Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {}
        }
    });
}
com.netflix.loadbalancer.LoadBalancerContext 代码片段
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
    String host = null;
    int port = -1;
    
    // Various Supported Cases
    // The loadbalancer to use and the instances it has is based on how it was registered
    // In each of these cases, the client might come in using Full Url or Partial URL
    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        // Partial URI or no URI Case
        // well we have to just get the right instances from lb - or we fall back
        if (lb != null){
            Server svc = lb.chooseServer(loadBalancerKey);
            return svc;
        } 
    } else {
        // Full URL Case
        // This could either be a vipAddress or a hostAndPort or a real DNS
        // if vipAddress or hostAndPort, we just have to consult the loadbalancer
        // but if it does not return a server, we should just proceed anyways
        // and assume its a DNS
        // For restClients registered using a vipAddress AND executing a request
        // by passing in the full URL (including host and port), we should only
        // consult lb IFF the URL passed is registered as vipAddress in Discovery
        boolean shouldInterpretAsVip = false;
        if (lb != null) {
            shouldInterpretAsVip = isVipRecognized(original.getAuthority());
        }
        if (shouldInterpretAsVip) {
            Server svc = lb.chooseServer(loadBalancerKey);
            if (svc != null){
                return svc;
            } 
        }
    }

    return new Server(host, port);
}

        至此,服务实例负载均衡筛选完毕。

        接着进入请求执行,我们返回到 SynchronousMethodHandler ,它调用execute() 方法。

feign.Client 代码片段
@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection, request);
}
4、客户端请求重试机制

        通过 FeignClientProperties 可以配置重试,重试类在 FeignClientFactoryBean 中实例化,实例化的逻辑是 configureFeign 方法中,先从容器中查找 Retryer 中的 bean,如果有则填充到 Feign.Builder,再从 FeignClientProperties 中查找配置,如果有则再次填充 Feign.Builder,简单的讲就是覆盖逻辑。

org.springframework.cloud.openfeign.FeignClientFactoryBean 代码片段
protected void configureFeign(FeignContext context, Feign.Builder builder) {
	FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);
	if (properties != null) {
		if (properties.isDefaultToProperties()) {
			//加载全局配置
			configureUsingConfiguration(context, builder);
			//加载默认配置
			configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
			//加载指定客户端的配置
			configureUsingProperties(properties.getConfig().get(this.contextId), builder);
		}
		else {
			configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
			configureUsingProperties(properties.getConfig().get(this.contextId), builder);
			configureUsingConfiguration(context, builder);
		}
	}
	else {
		configureUsingConfiguration(context, builder);
	}
}

        而feign真正执行重试请求的逻辑在代理类 SynchronousMethodHandler 中,该类是JDK动态代理后,最终执行的方法处理器。

feign.SynchronousMethodHandler 代码片段
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    //循环,若调用无异常则立即返回;有异常,进入重试,计算重试时间间隔,通过线程休眠进行等待重新调用
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          //这里重试,无异常则代表可以继续重试
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

        从上述流程看出,若调用异常,就会通过 Retryer 重试处理器进入重试流程。

feign.Retryer 代码片段
//默认实现
class Default implements Retryer {
	//最大重试次数
    private final int maxAttempts;
    //重试周期
    private final long period;
    //最大重试周期
    private final long maxPeriod;
    //重试次数
    int attempt;
    long sleptForMillis;

    public Default() {
      this(100, SECONDS.toMillis(1), 5);
    }

    public Default(long period, long maxPeriod, int maxAttempts) {
      this.period = period;
      this.maxPeriod = maxPeriod;
      this.maxAttempts = maxAttempts;
      this.attempt = 1;
    }

    public void continueOrPropagate(RetryableException e) {
      //重试次数 大于 最大重试次数,则抛出异常
      if (attempt++ >= maxAttempts) {
        throw e;
      }
	  
	  //计算重试的时间间隔
      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        //线程阻塞、休眠
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
        throw e;
      }
      sleptForMillis += interval;
    }

    long nextMaxInterval() {
      long interval = (long) (period * Math.pow(1.5, attempt - 1));
      return interval > maxPeriod ? maxPeriod : interval;
    }

    @Override
    public Retryer clone() {
      return new Default(period, maxPeriod, maxAttempts);
    }
  }

//不重试实现(也是Feign默认的重试机制)
Retryer NEVER_RETRY = new Retryer() {
    @Override
    public void continueOrPropagate(RetryableException e) {
      throw e;
    }
    @Override
    public Retryer clone() {
      return this;
    }
  };

        我们知道 Spring Cloud 为 Feign 集成了 Ribbon ,提供了 Http 客户端的负载均衡支持。不仅自身提供了重试机制,也支持 Ribbon 的 重试机制。

        在容器启动期间,根据类路径下是否有 spring-retry jar 包(即是有没 RetryTemplate 类型),创建了 RibbonLoadBalancedRetryFactory 工厂实例,用于在创建 Feign 客户端负载均衡时,创建可重试实例。

org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration 代码片段
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
		SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
	
	// LoadBalancedRetryFactory ,来源于 RibbonAutoConfiguration
	return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}

        我们直接进入 Feign 执行重试请求的代理类 SynchronousMethodHandler

feign.SynchronousMethodHandler 代码片段
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    Response response;
    try {
      //客户端执行请求,client 的实例是 LoadBalancerFeignClient 类型
      response = client.execute(request, options);
    } catch (IOException e) {
      throw errorExecuting(request, e);
    }

    boolean shouldClose = true;
    try {
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

        进入 LoadBalancerFeignClient 的 execute 方法查看到会调用 CachingSpringLoadBalancerFactory 创建 Feign 的负载均衡器。

org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory 代码片段
public FeignLoadBalancer create(String clientName) {
	FeignLoadBalancer client = this.cache.get(clientName);
	if (client != null) {
		return client;
	}
	IClientConfig config = this.factory.getClientConfig(clientName);
	ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
	ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
			ServerIntrospector.class);
	client = this.loadBalancedRetryFactory != null
			? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory)
			: new FeignLoadBalancer(lb, config, serverIntrospector);
	this.cache.put(clientName, client);
	return client;
}

        RetryableFeignLoadBalancer 是 Feign 可重试负载均衡客户端实例,利用了 spring retry 去重试失败的请求。类图如下:

在这里插入图片描述
        在创建好实例之后,执行 executeWithLoadBalancer 方法,它在 AbstractLoadBalancerAwareClient 父类中,最终还是调用子类实例的 execute 方法。

org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer 代码片段
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    //构造负载均衡的指令
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                    	//执行请求
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {}
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {} 
}

@Override
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) throws IOException {
	final Request.Options options;
	if (configOverride != null) {
		//加载、设置ribbon的承诺书配置
		RibbonProperties ribbon = RibbonProperties.from(configOverride);
		options = new Request.Options(ribbon.connectTimeout(this.connectTimeout), ribbon.readTimeout(this.readTimeout));
	}
	else {
		options = new Request.Options(this.connectTimeout, this.readTimeout);
	}
	//创建重试策略
	final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);
	//创建重试模板
	RetryTemplate retryTemplate = new RetryTemplate();
	//创建回退策略
	BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
	retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
	//创建重试监听器
	RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(this.getClientName());
	if (retryListeners != null && retryListeners.length != 0) {
		retryTemplate.setListeners(retryListeners);
	}
	//根据条件是否开启重试策略,或者代理
	retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
			: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this,this.getClientName()));
	return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
		//重试执行回调
		@Override
		public RibbonResponse doWithRetry(RetryContext retryContext)throws IOException {
			Request feignRequest = null;
			// on retries the policy will choose the server and set it in the context
			// extract the server and update the request being made
			if (retryContext instanceof LoadBalancedRetryContext) {
				ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance();
				if (service != null) {
					feignRequest = ((RibbonRequest) request
							.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
				}
			}
			if (feignRequest == null) {
				feignRequest = request.toRequest();
			}
			Response response = request.client().execute(feignRequest, options);
			if (retryPolicy != null && retryPolicy.retryableStatusCode(response.status())) {
				byte[] byteArray = response.body() == null ? new byte[] {}
						: StreamUtils.copyToByteArray(response.body().asInputStream());
				response.close();
				throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response,byteArray, request.getUri());
			}
			return new RibbonResponse(request.getUri(), response);
		}
	}, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() {
		//兜底执行回调
		@Override
		protected RibbonResponse createResponse(Response response, URI uri) {
			return new RibbonResponse(uri, response);
		}
	});
}

引用

官方文档
Spring Cloud的 Feign和Ribbon重试机制的误区
配置Feign重试机制

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现码+数据集+超详细注释.zip
提供的码资涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些码资特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资都能为你提供宝贵的学习和实践机会。通过学习和运行这些码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些码资进行课程实践、课外项目或毕业设计。通过分析和运行码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保码资的可运行性和易用性,特别注意了以下几点:首先,每份码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些码资,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值