Ribbon源碼解析

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

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

@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

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

  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就比較核心了ui

LoadBalancerInterceptor

客戶端請求攔截器this

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請求

1

相關文章
相關標籤/搜索