• Ribbon源码解析


    基于 spring-cloud-starter-netflix-ribbon-2.2.5.RELEASE

    1. 测试用例

    // ================== 配置
    /**
     * 

    让restTemplate具备Ribbon负载均衡的能力 *

    因为 {@link LoadBalancerAutoConfiguration} 中的 restTemplates字段上的注解@LoadBalanced, 所以以下必须注解上 @LoadBalanced */ @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } // ================== 应用 @Autowired // 使用时就不需要注解@LoadBalanced了 RestTemplate restTemplate; public String ribbonTest() { return restTemplate.getForEntity("http://micro-provider-srv/hi?id=" + UUID.randomUUID().toString(), String.class).getBody(); }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2. 源码解读

    2.1 AutoConfig之 LoadBalancerAutoConfiguration

    本配置类的主要功能:

    1. 从容器中收集注解了@LoadBalancedRestTemplate实例。
    2. 借助 RestTemplateCustomizer扩展将 LoadBalancerInterceptor实例应用到上一步收集到的每个RestTemplate实例中。
    // ===== 注意本类位于 spring-cloud-commons-2.2.5.RELEASE.jar。另外一个同名类则是位于 spring-cloud-loadbalancer-2.2.5.RELEASE.jar (这个是springcloud用来替代Ribbon的)
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class) // 和下方将要讲到的 RibbonAutoConfiguration 呼应
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {
    	// 1. 收集满足规则的 RestTemplate Bean
    	@LoadBalanced
    	@Autowired(required = false)
    	private List<RestTemplate> restTemplates = Collections.emptyList();
    
    	@Bean
    	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    		// 基于Spring扩展机制, 向容器中注入SmartInitializingSingleton实现类, 目的是为每个RestTemplate实例, 回调自定义RestTemplateCustomizer扩展实现。
    		// 下方紧跟着的代码就是向容器中注入一个RestTemplateCustomizer实现类
    		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) {
    			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    		}
    
    		//向容器中注入一个RestTemplateCustomizer实现类, 于上面应用RestTemplateCustomizer的代码逻辑呼应。
    		@Bean
    		@ConditionalOnMissingBean
    		public RestTemplateCustomizer restTemplateCustomizer(
    				final LoadBalancerInterceptor loadBalancerInterceptor) {
    			return restTemplate -> {
    				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
    						restTemplate.getInterceptors());
    				list.add(loadBalancerInterceptor);
    				// 为RestTemplatet添加Interceptor扩展
    				restTemplate.setInterceptors(list);
    			};
    		}
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    2.2 AutoConfig之 RibbonAutoConfiguration

    本配置类的主要功能:

    1. 收集向容器中注册的RibbonClientSpecification实例(NamedContextFactory.Specification实现类)。作为将其作为参数向容器中注册为SpringClientFactory实例。注:这一点和 FeignAutoConfiguration中的操作相同,区别只在于后者收集的配置Bean是FeignClientSpecification,注入到容器中的FeignContext实例。
    2. 向容器中注入其它必要的Ribbon辅助实例。
    // ============= 为了聚焦,代码有删减。
    @Configuration
    @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
    @RibbonClients // 所以如果没有特殊配置需要, 不需要在项目中显式引入该注解。
    @AutoConfigureAfter(
    		name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
    @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
    		AsyncLoadBalancerAutoConfiguration.class }) // 注意本配置类的执行时机是在 LoadBalancerAutoConfiguration 之前(这个LoadBalancerAutoConfiguration )。下方有相关介绍。
    @EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
    		ServerIntrospectorProperties.class })
    public class RibbonAutoConfiguration {
    
    	// 1. 收集向容器中注入的RibbonClientSpecification实例. 该实例的注入可以参见 由注解@RibbonClients引入的RibbonClientConfigurationRegistrar里的实现。 
    	@Autowired(required = false)
    	private List<RibbonClientSpecification> configurations = new ArrayList<>();
    
    	@Autowired
    	private RibbonEagerLoadProperties ribbonEagerLoadProperties;
    
    	@Bean
    	@ConditionalOnMissingBean
    	public SpringClientFactory springClientFactory() {
    		// SpringClientFactory实现自NamedContextFactory, 类似的还有FeignContext. 所以前者是Ribbon相关,后者是Feign相关, 两者原理和思路一样.
    		SpringClientFactory factory = new SpringClientFactory();
    		factory.setConfigurations(this.configurations);
    		return factory;
    	}
    
    	// 上面注解表明了本配置类生效时机早于LoadBalancerAutoConfiguration, 而这里的注入则会满足 LoadBalancerAutoConfiguration 生效的条件之一。
    	@Bean
    	@ConditionalOnMissingBean(LoadBalancerClient.class)
    	public LoadBalancerClient loadBalancerClient() {
    		return new RibbonLoadBalancerClient(springClientFactory());
    	}
    
    	@Bean
    	@ConditionalOnMissingBean
    	public PropertiesFactory propertiesFactory() {
    		return new PropertiesFactory();
    	}
    
    	// 响应配置项 ribbon.eager-load.enabled=true, 利用Spring的扩展机制, 通过响应ApplicationReadyEvent事件, 实现Ribbon配置子容器的早期加载.
    	@Bean
    	@ConditionalOnProperty("ribbon.eager-load.enabled")
    	public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
    		return new RibbonApplicationContextInitializer(springClientFactory(),
    				ribbonEagerLoadProperties.getClients());
    	}
    
    	...... 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    关于注解@RibbonClients

    1. 正如上面讲解过的,除非特殊配置需要,否则该注解无需在项目中进行显式声明。
    2. 该注解会导致 RibbonClientConfigurationRegistrar 的启用。
    3. RibbonClientConfigurationRegistrar参与Spring生命周期,根据@RibbonClients配置项向容器中注入 RibbonClientSpecification (继承自 NamedContextFactory.Specification ) 并最终被收集到 SpringClientFactory 中(注意看SpringClientFactory 的基类)。
    4. RibbonClientConfiguration 。 被 SpringClientFactory 直接在构造函数中写死方式引入
    5. 而对于SpringClientFactory,其会在 OkHttpRibbonCommandFactoryHttpClientRibbonCommandFactory 等中使用,目的是获取各个client对应的自定义配置项。
    2.3 执行阶段 - LoadBalancerInterceptor(实现负载均衡的入口)

    LoadBalancerInterceptor的内部实现其实很简单:拦截每个请求,将控制权转移给LoadBalancerClient实现者(本例中是RibbonLoadBalancerClient),进而实现客户端的负载均衡。

    // 本类定义在 spring-cloud-commons-2.2.5.RELEASE.jar 中。
    public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    	// LoadBalancerClient接口也是定义在spring-cloud-commons-2.2.5.RELEASE.jar 中。本例中实际实现类为 RibbonLoadBalancerClient。
    	private LoadBalancerClient loadBalancer;
    
    	@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,
    				this.requestFactory.createRequest(request, body, execution));
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2.4 执行阶段 - RibbonLoadBalancerClient(负载均衡的实际实现)

    作为LoadBalancerClient实现类,我们重点关注其实现的chooseexecutereconstructURI 方法。

    1. 实现choose(String serviceId)方法。

    1. 该方法实际是RibbonLoadBalancerClient对于其所继承接口ServiceInstanceChooser的实现。
    2. 本方法负责实现客户端的负载均衡,由传入的条件,按照预先制定规则选举出对应服务下的某个节点。
    // RibbonLoadBalancerClient.java 对于ServiceInstanceChooser接口的实现
    public ServiceInstance choose(String serviceId, Object hint) {
    	// 借助Ribbon机制, 选举出对应于当前serviceId的服务节点Server。
    	// 注意Server类型定义在 ribbon-loadbalancer-2.3.0.jar 中。
    	// 也正是在这个逻辑中, 最终将会回调用户定义的IRule实现。
    	Server server = getServer(getLoadBalancer(serviceId), hint);
    	if (server == null) {
    		return null;
    	}
    	// 将选举出的Server适配为RibbonServer(ServiceInstance接口的实现类)。
    	// 注意RibbonServer类型定义spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar 中。
    	return new RibbonServer(serviceId, server, isSecure(server, serviceId),
    			serverIntrospector(serviceId).getMetadata(server));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 实现reconstructURI(ServiceInstance instance, URI original)方法。

    1. 本方法是由RibbonLoadBalancerClient所继承的直接接口LoadBalancerClient定义的。
    2. 该方法负责以选举出的服务节点信息为基础,拼接出发起最终访问请求时的http地址。
    @Override
    public URI reconstructURI(ServiceInstance instance, URI original) {
    	// 这里的instance, 正是在上一步choose(...)方法中筛选出来的。
    	Assert.notNull(instance, "instance can not be null");
    	String serviceId = instance.getServiceId();
    	RibbonLoadBalancerContext context = this.clientFactory
    			.getLoadBalancerContext(serviceId);
    
    	URI uri;
    	Server server;
    	if (instance instanceof RibbonServer) {
    		RibbonServer ribbonServer = (RibbonServer) instance;
    		server = ribbonServer.getServer();
    		uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
    	}
    	else {
    		server = new Server(instance.getScheme(), instance.getHost(),
    				instance.getPort());
    		IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
    		ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
    		uri = updateToSecureConnectionIfNeeded(original, clientConfig,
    				serverIntrospector, server);
    	}
    	// 拼接出发起最终访问请求时的http地址。
    	return context.reconstructURIWithServer(server, uri);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3. 实现execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request)方法。

    1. 负责发起http请求调用,获取响应。
    2. 注意其中使用RibbonStatsRecorder来做调用情况统计。外界可以通过向容器中注入扩展的RibbonLoadBalancerContext实现类来参与这部分统计过程,加入自定义逻辑。
      在这里插入图片描述

    3. Feign集成

    1. 在feign实例构建阶段,决定是否启用 loadbalancer是在 FeignClientFactoryBean.getTarget()中。(feign源码解析 - 初始化)

      	// FeignClientFactoryBean.java
      	<T> T getTarget() {
      		FeignContext context = applicationContext.getBean(FeignContext.class);
      		Feign.Builder builder = feign(context);
      		
      		// 如果url配置项为空, 则表明是非明确指向http://xxx的调用, 而是指向基于服务名的调用, 所以需要启用loadbalancer.
      		if (!StringUtils.hasText(url)) {
      			if (!name.startsWith("http")) {
      				url = "http://" + name;
      			}
      			else {
      				url = name;
      			}
      			url += cleanPath();
      			return (T) loadBalance(builder, context,
      					new HardCodedTarget<>(type, name, url));
      		}
      		
      		// .... 如果是指向明确http服务地址的调用, 代码略
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    2. 通过实现feign.Client接口(典型如LoadBalancerFeignClient ),保持外界的Feign调用习惯。同时在LoadBalancerFeignClient实现中将控制权调度给 ribbon中的 com.netflix.client.IClient 接口实现类(典型如FeignLoadBalancer)。所以在 LoadBalancerFeignClient类中你同时可以看到 feign.Clientcom.netflix.client.IClient 实现类(前者代表:LoadBalancerFeignClient,后者代表:FeignLoadBalancer )。

      // LoadBalancerFeignClient类型对于 feign.Client 的实现
      @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);
      		// 注意这里的构造函数中, 将内部feign.Client接口实现类 delegate(本例中就是实现了feign.Client的 OkHttpClient) 作为参数传入. 这会在接下来的FeignLoadBalancer中得到应用。
      		FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
      				this.delegate, request, uriWithoutHost);
      
      		IClientConfig requestConfig = getClientConfig(options, clientName);
      		// lbClient(clientName)返回值类型为 FeignLoadBalancer, 其实现了ribbon的 com.netflix.client.IClient 接口, 所以相当于请求处理控制权由feign转交给了 ribbon
      		// 注意这里FeignLoadBalancer 并没有直接调用对于ribbon的 com.netflix.client.IClient 接口的实现, 而是调用继承自基类的 executeWithLoadBalancer(...)方法。
      		// 在基类 executeWithLoadBalancer方法中主要做了两件事情:
      		//		1. 基于客户端负载均衡选举出的 com.netflix.loadbalancer.Server实例( ribbon-loadbalancer-2.3.0.jar),构建出最终能够直接访问的 http://ip:port/xx/yy 地址 。 这里的选举操作就会应用到 IRule 实现类。	
      		//  	2. 基于上一步,最终调用 FeignLoadBalancer对于ribbon的 com.netflix.client.IClient 接口的实现来拿到请求响应。
      		return lbClient(clientName)
      				.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
      	}
      	catch (ClientException e) {
      		IOException io = findIOException(e);
      		if (io != null) {
      			throw io;
      		}
      		throw new RuntimeException(e);
      	}
      }
      
      // FeignLoadBalancer.java类型对于 com.netflix.client.IClient的实现(ribbon接口)
      @Override
      public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
      		throws IOException {
      	Request.Options options;
      	if (configOverride != null) {
      		RibbonProperties override = RibbonProperties.from(configOverride);
      		options = new Request.Options(override.connectTimeout(this.connectTimeout),
      				override.readTimeout(this.readTimeout));
      	}
      	else {
      		options = new Request.Options(this.connectTimeout, this.readTimeout);
      	}
      	// 关键时这句,从request中取出 feign.Client实现类, 这就呼应上面的, 作为构造参数注入的delegate, 也就是 `feign.Client` 实现类 `feign.okhttp.OkHttpClient` 。
      	Response response = request.client().execute(request.toRequest(), options);
      	return new RibbonResponse(request.getUri(), response);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
    3. 最终整体逻辑调用链条如下:

      HystrixInvocationHandler.this.dispatch.get(method).invoke(args) // feign入口。类似的还有FeignInvocationHandler。属于feign中对于 InvocationHandler 的实现。
      	SynchronousMethodHandler.invoke(Object[] argv)
      		SynchronousMethodHandler.executeAndDecode(RequestTemplate template, Options options)
      			LoadBalancerFeignClient.execute(Request request, Request.Options options) // feign.Client实现类
      				FeignLoadBalancer.executeWithLoadBalancer(final S request, final IClientConfig requestConfig) // `com.netflix.client.IClient` ribbon接口实现类
      					FeignLoadBalancer.execute(RibbonRequest request, IClientConfig configOverride) // `com.netflix.client.IClient` ribbon接口实现类
      						feign.okhttp.OkHttpClient.execute(RibbonRequest request, IClientConfig configOverride) // feign.Client实现类
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    4. 对于LoadBalancerFeignClient 实例,SpringCloud是借助 spring-cloud-openfeign-core-2.2.5.RELEASE.jar中定义的 FeignRibbonClientAutoConfiguration来实现的。

      4.1 该类上采用 @Import方式引入 HttpClientFeignLoadBalancedConfigurationfeign.httpclient.enabled=true,默认就是它)和 OkHttpFeignLoadBalancedConfigurationfeign.okhttp.enabled=true) 和 DefaultFeignLoadBalancedConfiguration ,最终三选一。
      4.2 以上@Import方式引入的feign.Client,统一都是 LoadBalancerFeignClient 。 其内部的 delegate 字段则是各自的 OkHttpClient / ApacheHttpClient 等,来完成实际的请求调用。

    5. Spring容器里是没有这个 FeignLoadBalancer 实例的, 但是有 LoadBalancerFeignClient实例。所以前者是个过渡品。

    最后将feign定义的http抽象调用接口, 和ribbon定义的http抽象调用接口放在一起,对比直观感受下。

    ##########################################################  Feign
    /**
     * Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe.
     */
    public interface Client {
    
      // Request 和 Response 类型为final, 不允许外界扩展
      /**
       * Executes a request against its {@link Request#url() url} and returns a response.
       *
       * @param request safe to replay.
       * @param options options to apply to this request.
       * @return connected response, {@link Response.Body} is absent or unread.
       * @throws IOException on a network error connecting to {@link Request#url()}.
       */
      Response execute(Request request, Options options) throws IOException;  
    }
    
    
    ##########################################################  Ribbon
    /**
     * A client that can execute a single request. 
     */
    public interface IClient<S extends ClientRequest, T extends IResponse> {
    
    	// ClientRequest 和 IResponse 都允许外界扩展
    	/**
    	 * Execute the request and return the response. It is expected that there is no retry and all exceptions
         * are thrown directly.
    	 */
        public T execute(S request, IClientConfig requestConfig) throws Exception; 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4. Zuul集成

    Zuul与Ribbon的集成,入口是在类型 RibbonRoutingFilter 中。

    // ================================= RibbonRoutingFilter.java
    protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
    	Map<String, Object> info = this.helper.debug(context.getMethod(),
    			context.getUri(), context.getHeaders(), context.getParams(),
    			context.getRequestEntity());
    	
    	// command的实际类型, 视底层使用的http框架, 为 OkHttpRibbonCommand(对应okhttp), OkHttpRibbonCommand(对应apache httpclient)等
    	// RibbonCommand直接继承自 HystrixExecutable, 自身没有定义任何方法, 所以是个标识性接口. 这样的好处:
    	//	1. 一来实现了更强的自解释, 
    	//	2. 二来也是通过父类的泛型类型限制了返回值类型ClientHttpResponse, 这一隶属于spring-web体系下的接口. 因为RibbonCommand是在spring-cloud-netflix-zuul模块中定义的, 所以这个约束是合适的. 
    	也是因为复用了spring-web提供的ClientHttpResponse接口, java技术体系下的诸多http框架就能无缝扩展了. 这一点, 下一行代码我们马上就可以看到了.
    	RibbonCommand command = this.ribbonCommandFactory.create(context);
    	try {
    	    // execute()方法, 实际定义在抽象类 HystrixCommand。"由主线程切换到对应的服务线程池线程"的操作, 正是在这里面实现的
    	    // 返回值类型ClientHttpResponse属于spring-web模块,其中一个实现类为 RibbonHttpResponse, 负责将底层使用的http框架(apache httpclient, okhttp)的返回值适配到ClientHttpResponse.(这里Ribbon又进行了一层封装: HttpResponse). 
    	    // 所以最终的关系是: 
    	    //	1. HttpResponse接口的实现类负责将http框架(apache httpclient, okhttp)的返回值适配为ribbon IResponse. 毕竟com.netflix.client.http.HttpResponse就是直接继承自IResponse.
    	    //	2. RibbonHttpResponse则负责将HttpResponse适配为ClientHttpResponse. 和上面一样的原理, RibbonHttpResponse继承自ClientHttpResponse, 同时通过构造函数要求使用者传入一个HttpResponse接口实现类.
    		ClientHttpResponse response = command.execute();
    		this.helper.appendDebug(info, response.getRawStatusCode(),
    				response.getHeaders());
    		return response;
    	}
    	catch (HystrixRuntimeException ex) {
    		return handleException(info, ex);
    	}
    
    }
    
    // ================================= AbstractRibbonCommand.java (hystrix体系下) 
    // 上述 command.execute() 的执行, 最终会将控制权跨线程转交给 run() 方法, 如下:
    // AbstractRibbonCommand.run() ; 实现自基类HystrixCommand
    protected ClientHttpResponse run() throws Exception {
    	final RequestContext context = RequestContext.getCurrentContext();
    	
    	// 自身定义的抽象方法, 回调子类实现(例如 OkHttpRibbonCommand, HttpClientRibbonCommand)
    	// 也正是在
    	RQ request = createRequest();
    	RS response;
    
    	boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
    			&& ((AbstractLoadBalancingClient) this.client)
    					.isClientRetryable((ContextAwareRequest) request);
    	
    	// this.client 继承自 ribbon-loadbalancer中的AbstractLoadBalancerAwareClient , 自然也就是实现了ribbon IClient接口. 典型实现类: OkHttpLoadBalancingClient.
    	if (retryableClient) {
    		// 这里的方法调用来自 ribbon IClient
    		response = this.client.execute(request, config);
    	}
    	else { 
    	    // 这里的方法调用来自 AbstractLoadBalancerAwareClient, 不过最终依然会执行到 ribbon IClient的 execute 方法
    		response = this.client.executeWithLoadBalancer(request, config);
    	}
    	context.set("ribbonResponse", response);
    
    	// Explicitly close the HttpResponse if the Hystrix command timed out to
    	// release the underlying HTTP connection held by the response.
    	//
    	if (this.isResponseTimedOut()) {
    		if (response != null) {
    			response.close();
    		}
    	}
    
    	return new RibbonHttpResponse(response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    1. 最终整体逻辑调用链条如下(以okhttp作为底层http框架为例):
    // zuul --> riibon
    RibbonRoutingFilter.run()    //  【zuul】【主线程】
    	OkHttpRibbonCommand.execute()   // #主线程, 开始将任务提交给子线程。
    	                                //【ribbon + hystrix】其父类AbstractRibbonCommand, 三个泛型参数分别对应执行当前请求的Client,当前请求request,当前请求的响应response。); 返回值类型 ClientHttpResponse( 属于spring-web,其中一个实现类为 RibbonHttpResponse) 
    		OkHttpLoadBalancingClient.executeWithLoadBalancer()  //# 专门的服务线程池线程,该方法定义在基类AbstractLoadBalancerAwareClient. OkHttpRibbonCommand的基类 AbstractRibbonCommand.run() 中调用该方法
        		OkHttpLoadBalancingClient.execute(...)  // # 其实是实现了ribbon IClient.execute(...) ; 基于http框架(例如 okhttp)发起远程调用, 返回值类型 OkHttpRibbonResponse
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:

    1. RequestContext.getCurrentContext().addZuulRequestHeader(...) 方法添加的自定义HTTP Header,首先在Zuul的RibbonRoutingFilter中传递给Ribbon中的RibbonCommandContext,然后在OkHttpRibbonRequest中由RibbonCommandContext传递给okhttp3.Request, 最终实现http header的透传. (类似的还有RibbonApacheHttpRequest)
    2. zuul + ribbon的组合中, 如果想要使用okhttp替换默认的apache httpclient,需要作如下配置.
      ########## ribbon启用哪个http框架
      ribbon:
        # HttpClientRibbonConfiguration
        httpclient:         # 默认启用
          enabled: false   
        # OkHttpRibbonConfiguration
        okhttp:
          enabled: true 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    3. 上面这些 ribbon里的IResponse,spring-mvc里的ClientHttpResponse,它们之间如何关联上的?
    ### ribbon 继承链
    IResponse                              # ribbon-core
    	HttpResponse                       # ribbon-httpclient
    		OkHttpRibbonResponse           # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar
    		RibbonApacheHttpResponse	   # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar
    		
    ### spring-mvc 继承链
    ClientHttpResponse
    	RibbonHttpResponse                 # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar 。负责将 HttpResponse(上方有apache httpclient, okhttp等多个实现类)实例适配到 ClientHttpResponse. 结合RibbonCommand父类HystrixExecutable限制了其execute()方法返回值类型为ClientHttpResponse, RibbonCommand子类之一OkHttpRibbonCommand, 至此闭环完成.
    	OkHttp3ClientHttpResponse
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4. 补充

    4.1 RibbonLoadBalancerClient VS RibbonLoadBalancingHttpClient

    其实仔细观察这两个的区别还是挺明显的,但笔者在阅读源码期间碰壁好几次,因此这里特意总结下。

    1. 前者是基于 spring-cloud-commons-2.2.5.RELEASE.jar 定义的 LoadBalancerClient 接口

    2. 后者基于 ribbon-core-2.3.0.jar中定义的 com.netflix.client.IClient接口。 spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar

    3. 双方都有实现接口 ServiceInstanceChooser (spring-cloud-commons-2.2.5.RELEASE.jar 定义)。
      3.1 AbstractLoadBalancingClient对其实现是基于ribbon筛选出ribbon对象Server,然后再做一次封装为RibbonLoadBalancerClient.RibbonServer这一ServiceInstance实现类。(实现了从ribbon到spring-cloud-commons-2.2.5.RELEASE.jar 的适配。)

    4. 最后直观感受下层级关系:
      在这里插入图片描述

    5. 未完待续

    综上,其实只介绍了Ribbon中的执行主线逻辑,在实现负载均衡中的诸多考量与对应解决方案,本文不做更多的涉猎,感兴趣的读者可以自行查找资料,或者看看底部参考链接。

    6. 参考

    1. 深入理解Ribbon原理
    2. SpringBoot 之 脱离Eureka使用Ribbon
    3. feign源码解析 - 初始化
    4. feign源码解析 - 运行时
  • 相关阅读:
    grafana 画富集多个指标 label 的表格
    基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发
    粒子群算法——王者荣耀的视野共享辅助决策的底层原理
    国产域控TSN协议栈首发量产,理想汽车+映驰科技「抢先」
    Python 中 staticmethod 和 classmethod 原理
    Windows与网络基础-5-安装eNSP软件环境
    工程管理系统源码之全面+高效的工程项目管理软件
    【实习】vue input下拉及搜索功能
    进程之间的通信(管道详解)
    Linux学习-21-yum命令(查询、安装、升级和卸载软件包)和软件组管理
  • 原文地址:https://blog.csdn.net/lqzkcx3/article/details/127199120