Feign 机制

1,216 阅读3分钟

1. 负载均衡

自动配置会Import HttpClient,OkHttp,Default三种配置,FeignLoadBalancerAutoConfiguration配置在FeignAutoConfiguration前完成。

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
		matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
		OkHttpFeignLoadBalancerConfiguration.class,
		DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}

如果不做任何设置,并且引入了ApacheHttpClient,优先使用HttpClient.
其次使用OKHttp,需要配置feign.okhttp.enabled=true.
如果都没有则使用Client.default()实现,这个实现使用jdk的HttpURLConnection进行处理。 retry配置开启,关闭需要显示设置

spring.cloud.loadbalancer.retry.enabled=true

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {
	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory, HttpClient httpClient) {
		ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}
}

LoadBalancerFeignClient的execute实现

  • 创建RibbonRequest
  • 获取clientConfig
  • 执行request,并返回结果
public class LoadBalancerFeignClient implements Client {
	//code removed
	@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) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}
    //code removed
 }

2. 重试处理

public class LoadBalancerRetryProperties {
	//默认开启
	private boolean enabled = true;
	//是否允许除GET之外的方法重试
	private boolean retryOnAllOperations = false;
	//当前instance最多重试次数
	private int maxRetriesOnSameServiceInstance = 0;
	//下一个instance最多重试次数
	private int maxRetriesOnNextServiceInstance = 1;
	//需要重试的状态码
	private Set<Integer> retryableStatusCodes = new HashSet<>();
 }

RetryableFeignBlockingLoadBalancerClient的execute实现

  • 创建retryPolicy
  • 创建RetryTemplate,基于spring-retry
    • 具体的重试c
  • 执行request,并返回repsonse
public class RetryableFeignBlockingLoadBalancerClient implements Client {
   //code removed
   @Override
   public Response execute(Request request, Request.Options options) throws IOException {
   	final URI originalUri = URI.create(request.url());
   	String serviceId = originalUri.getHost();
   	final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory
   			.createRetryPolicy(serviceId, loadBalancerClient);
   	RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
   	return retryTemplate.execute(context -> {
   		Request feignRequest = null;
   		// On retries the policy will choose the server and set it in the context
   		// and extract the server and update the request being made
   		if (context instanceof LoadBalancedRetryContext) {
   			ServiceInstance serviceInstance = ((LoadBalancedRetryContext) context)
   					.getServiceInstance();
   			if (serviceInstance != null) {
   				if (LOG.isDebugEnabled()) {
   					LOG.debug(String.format(
   							"Using service instance from LoadBalancedRetryContext: %s",
   							serviceInstance));
   				}
   				String reconstructedUrl = loadBalancerClient
   						.reconstructURI(serviceInstance, originalUri).toString();
   				feignRequest = Request.create(request.httpMethod(), reconstructedUrl,
   						request.headers(), request.body(), request.charset(),
   						request.requestTemplate());
   			}
   		}
   		if (feignRequest == null) {
   			if (LOG.isWarnEnabled()) {
   				LOG.warn(
   						"Service instance was not resolved, executing the original request");
   			}
   			feignRequest = request;
   		}
   		Response response = delegate.execute(feignRequest, options);
   		int responseStatus = response.status();
   		if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
   			if (LOG.isDebugEnabled()) {
   				LOG.debug(
   						String.format("Retrying on status code: %d", responseStatus));
   			}
   			response.close();
   			throw new RetryableStatusCodeException(serviceId, responseStatus,
   					response, URI.create(request.url()));
   		}
   		return response;
   	}, new LoadBalancedRecoveryCallback<Response, Response>() {
   		@Override
   		protected Response createResponse(Response response, URI uri) {
   			return response;
   		}
   	});
   }

RetryTemplate的具体实现

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
			RecoveryCallback<T> recoveryCallback, RetryState state)
			throws E, ExhaustedRetryException {

		RetryPolicy retryPolicy = this.retryPolicy;
		BackOffPolicy backOffPolicy = this.backOffPolicy;

		// Allow the retry policy to initialise itself...
		RetryContext context = open(retryPolicy, state);
		if (this.logger.isTraceEnabled()) {
			this.logger.trace("RetryContext retrieved: " + context);
		}

		// Make sure the context is available globally for clients who need
		// it...
		RetrySynchronizationManager.register(context);

		Throwable lastException = null;

		boolean exhausted = false;
		try {

			// Give clients a chance to enhance the context...
			boolean running = doOpenInterceptors(retryCallback, context);

			if (!running) {
				throw new TerminatedRetryException(
						"Retry terminated abnormally by interceptor before first attempt");
			}

			// Get or Start the backoff context...
			BackOffContext backOffContext = null;
			Object resource = context.getAttribute("backOffContext");

			if (resource instanceof BackOffContext) {
				backOffContext = (BackOffContext) resource;
			}

			if (backOffContext == null) {
				backOffContext = backOffPolicy.start(context);
				if (backOffContext != null) {
					context.setAttribute("backOffContext", backOffContext);
				}
			}

			/*
			 * We allow the whole loop to be skipped if the policy or context already
			 * forbid the first try. This is used in the case of external retry to allow a
			 * recovery in handleRetryExhausted without the callback processing (which
			 * would throw an exception).
			 */
			while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

				try {
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Retry: count=" + context.getRetryCount());
					}
					// Reset the last exception, so if we are successful
					// the close interceptors will not think we failed...
					lastException = null;
					return retryCallback.doWithRetry(context);
				}
				catch (Throwable e) {

					lastException = e;

					try {
						registerThrowable(retryPolicy, state, context, e);
					}
					catch (Exception ex) {
						throw new TerminatedRetryException("Could not register throwable",
								ex);
					}
					finally {
						doOnErrorInterceptors(retryCallback, context, e);
					}

					if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
						try {
							backOffPolicy.backOff(backOffContext);
						}
						catch (BackOffInterruptedException ex) {
							lastException = e;
							// back off was prevented by another thread - fail the retry
							if (this.logger.isDebugEnabled()) {
								this.logger
										.debug("Abort retry because interrupted: count="
												+ context.getRetryCount());
							}
							throw ex;
						}
					}

					if (this.logger.isDebugEnabled()) {
						this.logger.debug(
								"Checking for rethrow: count=" + context.getRetryCount());
					}

					if (shouldRethrow(retryPolicy, context, state)) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug("Rethrow in retry for policy: count="
									+ context.getRetryCount());
						}
						throw RetryTemplate.<E>wrapIfNecessary(e);
					}

				}

				/*
				 * A stateful attempt that can retry may rethrow the exception before now,
				 * but if we get this far in a stateful retry there's a reason for it,
				 * like a circuit breaker or a rollback classifier.
				 */
				if (state != null && context.hasAttribute(GLOBAL_STATE)) {
					break;
				}
			}

			if (state == null && this.logger.isDebugEnabled()) {
				this.logger.debug(
						"Retry failed last attempt: count=" + context.getRetryCount());
			}

			exhausted = true;
			return handleRetryExhausted(recoveryCallback, context, state);

		}
		catch (Throwable e) {
			throw RetryTemplate.<E>wrapIfNecessary(e);
		}
		finally {
			close(retryPolicy, context, state, lastException == null || exhausted);
			doCloseInterceptors(retryCallback, context, lastException);
			RetrySynchronizationManager.clear();
		}

	}

3. 异常处理fallback

class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
				: factory.getContextId();
		SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(name, context, target, builder, fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(name, context, target, builder,
					fallbackFactory);
		}

		return feign.target(target);
	}

	private <T> T targetWithFallback(String feignClientName, FeignContext context,
			Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
			Class<?> fallback) {
		T fallbackInstance = getFromContext("fallback", feignClientName, context,
				fallback, target.type());
		return builder.target(target, fallbackInstance);
	}
    
    //code removed
}