Ribbon源碼解析


在上篇文章Ribbon架構剖析中,我們已經介紹了Ribbon的架構組成以及很多重要的對象,相信你已經對Ribbon已經有一個清晰的認識了。本篇文章則研究一下Ribbon的原理

首先我們知道,在普通項目中Ribbon的使用是這樣的

@SpringBootApplication
@RibbonClient(name = "provider-demo", configuration = cn.org.config.LoadBalanced.class)
public class CloudDemoConsumerApplication {
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate(){
		return new RestTemplate();
	}
	public static void main(String[] args) {
		SpringApplication.run(CloudDemoConsumerApplication.class, args);
	}
}

這里面最引人矚目的就是注解@RibbonClient了,看一下這個注解都是做了什么吧

@RibbonClient

觀察@RibbonClient的源碼可知,這個注解使用@Import注解引入了配置類RibbonClientConfigurationRegistrar,看一下這個類的registerBeanDefinitions方法

public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> attrs = metadata.getAnnotationAttributes(
				RibbonClients.class.getName(), true);
		if (attrs != null && attrs.containsKey("value")) {
			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
			for (AnnotationAttributes client : clients) {
				registerClientConfiguration(registry, getClientName(client),
						client.get("configuration"));
			}
		}
		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			} else {
				name = "default." + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					attrs.get("defaultConfiguration"));
		}
		Map<String, Object> client = metadata.getAnnotationAttributes(
				RibbonClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}
  1. 首先會判斷是否存在注解@RibbonClients,注意,這里可是多了一個s的
  2. 然后判斷@RibbonClients注解上是否存在屬性valuedefaultConfiguration,如果存在的話分別注冊他們
  3. 接着最后才是處理@RibbonClient注解
  4. 這里我們就可以猜測RibbonClientConfigurationRegistrar這個類應該是可以同時處理這兩個注解的,觀察一下@RibbonClients注解的源碼發現它確實是引入的也是這個類
  5. 這兩個注解的區別應該也可以猜測出來,單數和雙數
  6. 觀察最后注冊的代碼,可以看到最后注冊bean的類型都是RibbonClientSpecification,這里留意一下
	private void registerClientConfiguration(BeanDefinitionRegistry registry,
			Object name, Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}
自動裝配

上方看完這些代碼之后,我們了解了@RibbonClients@RibbonClient兩個注解,可以對整體的流程還是有些疑惑。那么接下來就看看自動裝配都是做了什么吧

查看Ribbon包下的spring.factories文件,發現引入了一個配置類RibbonAutoConfiguration,那么從這個類開始看起吧

先決條件
  1. @ConditionalOnClass,當前環境必須存在這幾個類: IClient, RestTemplate, AsyncRestTemplate, Ribbon
  2. @RibbonClients,這個注解剛才已經講過了,暫且不提
  3. @AutoConfigureAfter,負載均衡肯定是要基於注冊中心來做的,所以自動裝配是在Eureka初始化完畢之后初始化的
  4. @AutoConfigureBefore,這里的兩個類先不說,保持神秘
  5. @EnableConfigurationProperties,兩個配置類,其中:
    1. RibbonEagerLoadProperties類中是關於Ribbon的飢餓加載模式的屬性
    2. ServerIntrospectorProperties類中是關於安全端口的屬性
裝配bean

這個配置類加載的類挺多的,但是比較重要的有這幾個:

  1. SpringClientFactory,我們知道每一個微服務在都會調用多個微服務,而調用各個微服務的配置可能是不一樣的,所以就需要這個創建客戶端負載均衡器的工廠類,它可以為每一個ribbon客戶端生成不同的Spring上下文,而觀察這個類的configurations屬性也驗證了這一點
@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>();
    @Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
  1. RibbonLoadBalancerClient,持有SpringClientFactory對象,當然,它還有其他的功能,這里暫且不提
負載均衡

上方雖然看了Ribbon的自動裝配功能,但是好像離真相還有一些距離,這是因為雖然Ribbon准備好了,但是負載均衡還沒看呢。SpringCloud把負載均衡相關的自動配置放在了spring-cloud-commons包下
負載均衡的配置類是LoadBalancerAutoConfiguration

這個類里注冊的幾個bean就比較核心了

LoadBalancerInterceptor

客戶端請求攔截器

RestTemplateCustomizer

用於給所有的RestTemplate增加攔截器

@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
負載均衡核心實現

現在我們就可以猜測,整個核心應該就是在這個攔截器上了,看一看攔截器的核心方法:

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

其中requestFactory.createRequest(request, body, execution)方法是為了把請求參數封裝為request
重點關注execute方法

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}
創建負載均衡器

我們知道,每個Ribbon客戶端的負載均衡器都是唯一的,第一行getLoadBalancer就會去創建這個負載均衡器

   protected ILoadBalancer getLoadBalancer(String serviceId) {
		return this.clientFactory.getLoadBalancer(serviceId);
	}
   public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
	}
	public <C> C getInstance(String name, Class<C> type) {
		C instance = super.getInstance(name, type);
		if (instance != null) {
			return instance;
		}
		IClientConfig config = getInstance(name, IClientConfig.class);
		return instantiateWithConfig(getContext(name), type, config);
	}

最后的邏輯是如果存在緩存則從緩存中獲取,如果不存在創建

static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
										Class<C> clazz, IClientConfig config) {
		C result = null;
		
		try {
			Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
			result = constructor.newInstance(config);
		} catch (Throwable e) {
			// Ignored
		}
		
		if (result == null) {
			result = BeanUtils.instantiate(clazz);
			
			if (result instanceof IClientConfigAware) {
				((IClientConfigAware) result).initWithNiwsConfig(config);
			}
			
			if (context != null) {
				context.getAutowireCapableBeanFactory().autowireBean(result);
			}
		}
		
		return result;
	}

創建的大題流程則就是通過文章開始提到的兩個注解注冊的幾個RibbonClientSpecification類型的配置來創建

獲取服務

getServer方法的實現應該可以猜出來,使用具體的負載均衡器結合相應的負載均衡算法再加上服務列表過濾、服務健康檢測等操作最后會獲取的一個可用服務

調用服務

這里在調用之前把服務封裝成了RibbonServer

        private final String serviceId;
		private final Server server;
		private final boolean secure;
		private Map<String, String> metadata;

除了這幾個屬性外,RibbonServer還有一個方法

public URI getUri() {
			return DefaultServiceInstance.getUri(this);
		}

這個方法就把服務從實例id轉化為一個可調用的url了

	public static URI getUri(ServiceInstance instance) {
		String scheme = (instance.isSecure()) ? "https" : "http";
		String uri = String.format("%s://%s:%s", scheme, instance.getHost(),
				instance.getPort());
		return URI.create(uri);
	}

然后就是發送http請求

原文地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM