IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Ribbon源码解析 -> 正文阅读

[Java知识库]Ribbon源码解析

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

1. 测试用例

// ================== 配置
/**
 * <p> 让restTemplate具备Ribbon负载均衡的能力
 * <p> 因为 {@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();
}

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

	}

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());
	}

	...... 
}

关于注解@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));
	}

}

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

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

3. 实现execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> 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服务地址的调用, 代码略
    	}
    
  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);
    }
    
  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实现类
    
  4. 对于LoadBalancerFeignClient 实例,SpringCloud是借助 spring-cloud-openfeign-core-2.2.5.RELEASE.jar中定义的 FeignRibbonClientAutoConfiguration来实现的。

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

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

	RibbonCommand command = this.ribbonCommandFactory.create(context);
	try {
	    // execute()方法, 实际定义在 HystrixCommand
		ClientHttpResponse response = command.execute();
		this.helper.appendDebug(info, response.getRawStatusCode(),
				response.getHeaders());
		return response;
	}
	catch (HystrixRuntimeException ex) {
		return handleException(info, ex);
	}

}

// 上述 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 Client接口
	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. 最终整体逻辑调用链条如下:
RibbonRoutingFilter.run()
	RibbonCommand.execute()  //  RibbonCommand ( 其直接子类AbstractRibbonCommand ,三个泛型参数分别对应执行当前请求的Client,当前请求request,当前请求的响应response )
		AbstractLoadBalancerAwareClient.executeWithLoadBalancer()

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源码解析 - 运行时
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 11:40:46  更:2022-10-31 11:42:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:50:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码