【源码】Spring Cloud —— Ribbon 4 Ribbon 与 OpenFeign、RestTemplate 的整合

前言

至此,对 Ribbon 的部分核心接口和实现类做了解读。通常的,我们使用 Spring Cloud 单独整合 Ribbon 时,可以通过在 RestTemplate 实例上添加 @LoadBalanced 注解,实现 RestTemplate 发送请求的 负载均衡。同样,在 Spring Cloud 整合 OpenFeign 使用时,默认也会将 Ribbon 整合进去

本章节,结合部分源码对 RibbonOpenFeignRestTemplate 的整合做相关解读

版本

Spring Cloud Netflix 版本:2.2.3.RELEASE

Ribbon 与 RestTemplate

通过 @LoadBalanced 注解对 RestTemplate 实例做标记,然后赋予它 负载均衡 的能力,详情见核心配置类 LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	/**
	 * 收集所有的 RestTemplate 实例
	 */
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	// ...
	
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {

		/**
		 * 使用容器中的 RestTemplateCustomizer
		 * 		自定义处理所有的 RestTemplate 实例
		 */
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

	// ...

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {

			/**
			 * 容器默认提供的 LoadBalancerInterceptor 实例
			 */
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			/**
			 * 由 LoadBalancerInterceptor 拦截处理
			 */
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}
}
  • 收集 RestTemplate 实例
  • 所有 RestTemplate 交由 RestTemplateCustomizer 自定义处理
  • 同时,提供了 RestTemplateCustomizer 的一个实例,该实例为 RestTemplate 添加一个拦截类 LoadBalancerInterceptor
  • 同时,提供了 LoadBalancerInterceptor 的实例

LoadBalancerInterceptor

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;

	private LoadBalancerRequestFactory requestFactory;

	// ...

	@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);

		/**
		 * 拦截逻辑,即交给 LoadBalancerClient#execute 方法~
		 */
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}
}

拦截逻辑即交给 LoadBalancerClient#execute 方法,于是便回到了第二章节的内容,便不再多说了

Ribbon 与 OpenFeign

在之前解读 OpenFeign 的文章中,我们了解到 FeignContext 上下文提供的 Client 实例都被包装成 LoadBalancerFeignClient

其本质是一层装饰,对于不需要 负载均衡 的场景,取
LoadBalancerFeignClient.delegate 属性即可

关于 OpenFeign 解读的传送门:

【源码】Spring Cloud —— OpenFeign 2 FeignClientFactoryBean 等

最终的请求由 LoadBalancerFeignClient#execute 方法发出

LoadBalancerFeignClient#execute

	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 是基于
			 * 		this.delegate 创建的
			 */
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);

			/**
			 * lbClient:获取 FeignLoadBalancer 缓存对象
			 * executeWithLoadBalancer:选择对应的 server 实例发送请求
			 * toResponse:返回结果
			 */
			return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
					requestConfig).toResponse();
		}
		catch (ClientException e) {
			// ...
		}
	}

根据方法名 executeWithLoadBalancer 不难猜测 负载均衡 逻辑由该方法实现

AbstractLoadBalancerAwareClient#executeWithLoadBalancer

该方法由 FeignLoadBalancer 的抽象父类 AbstractLoadBalancerAwareClient 提供

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

        try {
        	
        	/**
        	 * LoadBalancerCommand#submit 方法传入一个 ServerOperation 对象,返回一个 Observable 实例
        	 */
            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 {
                        	/**
                        	 * 最终还是委托给 FeignLoadBalancer#execute 执行
                        	 */
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            // ...
        }
        
    }

获取对应的 LoadBalancerCommand 对象,创建匿名 ServerOperation 对象,执行 submit 方法

LoadBalancerCommand#submit

	public Observable<T> submit(final ServerOperation<T> operation) {
        
        // ...

        /**
         * selectServer 方法选择对应的 Server 实例发送请求
         */
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    
                    // ...
	
					return operation.call(server).doOnEach(new Observer<T>() {
                                            
                    	// ...
                
                });
            
        // ...
   
    }
  • selectServer 方法选择对应的 Server 实例发送请求,其最终由 ILoadBalancer#chooseServer 方法选取对应的 Server,即回到第二章节的内容
  • 此处使用 RxJava,发射 Server 实例,执行对应逻辑,最后交由 ServerOperation#call 方法执行
  • 此处的 ServerOperation 实例即 LoadBalancerCommand#submit 方法的入参,由 AbstractLoadBalancerAwareClient#executeWithLoadBalancer 匿名构造,将方法委托给 FeignLoadBalancer#execute 执行
  • FeignLoadBalancer#execute 的实现基于 FeignLoadBalancer.RibbonRequest 实现,而 FeignLoadBalancer.RibbonRequest 基于 LoadBalancerFeignClient.delegate 属性构造,也就是说最终请求的发送由对应具体的 Client 负责(LoadBalancerFeignClient 作为 装饰者,仅仅拓展了 负载均衡 的能力,装饰者设计模式 的经典应用)

小结

整个调用链路比较绕,作个 时序图 总结一下:

时序图

总结

至此,Ribbon 的解读就结束了,类似于 OpenFeign 的实现,每个 Ribbon 实例都有自己的上下文,因此 自定义配置 就十分灵活了

上一篇:【源码】Spring Cloud —— Ribbon 3 IRule

参考

《Spring Cloud 微服务架构进阶》 —— 朱荣鑫 张天 黄迪璇

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值