ribbon源码分析之LoadBalancerAutoConfiguration

在上一博客ribbon源码分析之自定义配置、全局配置中我们已经初步了解了rebbon是如何自动配置如何进行自定义配置

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {

那么就接探究LoadBalancerAutoConfiguration部分吧

@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

首先自动注入被@LoadBalanced标记的restTemplates,不难看出@LoadBalanced是一个标记注解:

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

自动注入实现了LoadBalancerRequestTransformer的列表,用于对reques增加一些定制化需求

@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

接下来是LoadBalancerInterceptorConfig

@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}
  1. 注册一个LoadBalancerInterceptor
  2. 把自动注入的restTemplate中添加拦截器LoadBalancerInterceptor
@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}
  1. 注册一个RetryLoadBalancerInterceptor
  2. 把自动注入的restTemplate中添加拦截RetryLoadBalancerInterceptor

LoadBalancerInterceptor有两个成员变量LoadBalancerClient和LoadBalancerRequestFactory

LoadBalancerClient负责重组url和执行请求

public interface LoadBalancerClient extends ServiceInstanceChooser {

	/**
	 * Executes request using a ServiceInstance from the LoadBalancer for the specified
	 * service.
	 * @param serviceId The service ID to look up the LoadBalancer.
	 * @param request Allows implementations to execute pre and post actions, such as
	 * incrementing metrics.
	 * @return The result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance.
	 */
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Executes request using a ServiceInstance from the LoadBalancer for the specified
	 * service.
	 * @param serviceId The service ID to look up the LoadBalancer.
	 * @param serviceInstance The service to execute the request to.
	 * @param request Allows implementations to execute pre and post actions, such as
	 * incrementing metrics.
	 * @return The result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance.
	 */
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Creates a proper URI with a real host and port for systems to utilize.
	 * Some systems use a URI with the logical service name as the host,
	 * such as http://myservice/path/to/service.  This will replace the
	 * service name with the host:port from the ServiceInstance.
	 * @param instance
	 * @param original A URI with the host as a logical service name.
	 * @return A reconstructed URI.
	 */
	URI reconstructURI(ServiceInstance instance, URI original);
}

并且继承ServiceInstanceChooser类,这个类提供了choose方法:选择具体一个服务实例

public interface ServiceInstanceChooser {

    /**
     * Chooses a ServiceInstance from the LoadBalancer for the specified service.
     * @param serviceId The service ID to look up the LoadBalancer.
     * @return A ServiceInstance that matches the serviceId.
     */
    ServiceInstance choose(String serviceId);
}

LoadBalancerRequestFactory的作用是为 LoadBalancerInterceptor 和 RetryLoadBalancerInterceptor 创建 LoadBalancerRequests。将 LoadBalancerRequestTransformers 应用于拦截的 HttpRequest。(实现LoadBalancerRequestTransformer接口客制化一些需求)

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
			final byte[] body, final ClientHttpRequestExecution execution) {
		return instance -> {
            HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
            if (transformers != null) {
                for (LoadBalancerRequestTransformer transformer : transformers) {
                    serviceRequest = transformer.transformRequest(serviceRequest, instance);
                }
            }
            return execution.execute(serviceRequest, body);
        };
	}

LoadBalancerInterceptor只有一个方法intercept

@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}

获得originalUri和serviceName使用requestFactory.createRequest获得真实的调用地址,调用LoadBalancerClient的execute方法执行请求。

而RetryLoadBalancerInterceptor的intercept方法如下

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
										final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		final String serviceName = originalUri.getHost();
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
		//获取重试策略
		final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory.createRetryPolicy(serviceName,
				loadBalancer);
		RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
		return template.execute(context -> {
			ServiceInstance serviceInstance = null;
			if (context instanceof LoadBalancedRetryContext) {
				LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
				//获取服务实例
				serviceInstance = lbContext.getServiceInstance();
			}
			if (serviceInstance == null) {
				serviceInstance = loadBalancer.choose(serviceName);
			}
			//执行请求
			ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer.execute(
					serviceName, serviceInstance,
					requestFactory.createRequest(request, body, execution));
			int statusCode = response.getRawStatusCode();
			//如果重试策略不为null并且返回code在配置的code中,抛出异常,可以做到根据返回码来控制是否要重试
			if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
				byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
				response.close();
				throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy);
			}
			return response;
		}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
			//This is a special case, where both parameters to LoadBalancedRecoveryCallback are
			//the same.  In most cases they would be different.
			@Override
			protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) {
				return response;
			}
		});
	}

比之LoadBalancerInterceptor的成员变量,不同的是RibbonLoadBalancedRetryFactory,
RibbonLoadBalancedRetryFactory主要负责创建重试策略、重试监听等

public interface LoadBalancedRetryFactory {

	/**
	 * Creates a {@link LoadBalancedRetryPolicy}.
	 * @param service The ID of the service to create the retry policy for.
	 * @param serviceInstanceChooser Used to get the next server from a load balancer.
	 * @return A retry policy for the service.
	 */
	default LoadBalancedRetryPolicy createRetryPolicy(String service, ServiceInstanceChooser serviceInstanceChooser) {
		return null;
	}

	/**
	 * Creates an array of {@link RetryListener}s for a given service.
	 * @param service The service to create the {@link RetryListener}s for.
	 * @return An array of {@link RetryListener}s.
	 */
	default RetryListener[] createRetryListeners(String service) {
		return new RetryListener[0];
	}

	/**
	 * Creates a {@link BackOffPolicy} for a given service.
	 * @param service The service to create the {@link BackOffPolicy} for.
	 * @return The {@link BackOffPolicy}.
	 */
	default BackOffPolicy createBackOffPolicy(String service) {
		return new NoBackOffPolicy();
	}
}

RibbonLoadBalancedRetryPolicy主要用来判断是否能够满足重试条件,主要代码如下


    /**
     * Return true to retry the failed request on the same server.
     * This method may be called more than once when executing a single operation.
     * @param context The context for the retry operation.
     * @return True to retry the failed request on the same server; false otherwise.
     */
    public boolean canRetrySameServer(LoadBalancedRetryContext context);

    /**
     * Return true to retry the failed request on the next server from the load balancer.
     * This method may be called more than once when executing a single operation.
     * @param context The context for the retry operation.
     * @return True to retry the failed request on the next server from the load balancer; false otherwise.
     */
    public boolean canRetryNextServer(LoadBalancedRetryContext context);

    /**
     * Called when the retry operation has ended.
     * @param context The context for the retry operation.
     */
    public abstract void close(LoadBalancedRetryContext context);

    /**
     * Called when the execution fails.
     * @param context The context for the retry operation.
     * @param throwable The throwable from the failed execution.
     */
    public abstract void registerThrowable(LoadBalancedRetryContext context, Throwable throwable);

    /**
     * If an exception is not thrown when making a request, this method will be called to see if the
     * client would like to retry the request based on the status code returned.  For example, in
     * Cloud Foundry, the router will return a <code>404</code> when an app is not available.  Since
     * HTTP clients do not throw an exception when a <code>404</code> is returned,
     * <code>retryableStatusCode</code> allows clients to force a retry.
     * @param statusCode The HTTP status code.
     * @return True if a retry should be attempted; false to just return the response.
     */
    public boolean retryableStatusCode(int statusCode);
@Override
	public boolean canRetrySameServer(LoadBalancedRetryContext context) {
		return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context);
	}

	@Override
	public boolean canRetryNextServer(LoadBalancedRetryContext context) {
		//this will be called after a failure occurs and we increment the counter
		//so we check that the count is less than or equals to too make sure
		//we try the next server the right number of times
		return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer() && canRetry(context);
	}

其中canRetry接口用来判断请求类型是否允许重试,默认只允许GET请求能够重试,如果需要所有的请求都能实现重试,那么需要设置OkToRetryOnAllOperations为true(当然不建议这么做)。

public boolean canRetry(LoadBalancedRetryContext context) {
		HttpMethod method = context.getRequest().getMethod();
		return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
	}

canRetryNextServer主要用来判断是否满足调用下一个服务的条件。lbContext.getRetryHandler()是默认的DefaultLoadBalancerRetryHandler,

protected RetryHandler defaultRetryHandler = new DefaultLoadBalancerRetryHandler();
public DefaultLoadBalancerRetryHandler() {
        this.retrySameServer = 0;
        this.retryNextServer = 0;
        this.retryEnabled = false;
    }

retrySameServer:默认情况下是0,即重试自身服务的次数为0,
retryNextServer:默认情况下是0,即重试下个服务的次数为0,
retryEnabled:即是否说有请求都重试,默认是false。

到此可以总结

  1. 自动注入@LoadBalanced标记的restTemplates
  2. 自动注入实现了LoadBalancerRequestTransformer的transformers
  3. 创建LoadBalancerRequestFactory,为 LoadBalancerInterceptor 和 RetryLoadBalancerInterceptor 创建 LoadBalancerRequests。将 LoadBalancerRequestTransformers 应用于拦截的 HttpRequest。
  4. 为restTemplate添加LoadBalancerInterceptor和RetryInterceptorAutoConfiguration

负载均衡的内容关于LoadBalancerClient的具体实现也尤为重要(LoadBalancerRequestFactory中通过LoadBalancerClient来执行请求),将在下一篇博客中单独介绍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值