Ribbon source code analysis

In the previous article, Ribbon Architecture Analysis , we have introduced Ribbon's architecture and many important objects. I believe you already have a clear understanding of Ribbon. This article studies the principle of Ribbon

First of all, we know that the use of Ribbon in ordinary projects is like this

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

The most eye-catching thing here is the annotation @RibbonClient. Let's see what this annotation does.

@RibbonClient

The observed @RibbonClientsource code shows that this annotation uses the @Importannotation to introduce the configuration class RibbonClientConfigurationRegistrar. Let's take a look at the registerBeanDefinitionsmethod of this class.

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. First, it will determine whether there is an annotation @RibbonClients. Note that there is an extra s here.
  2. Then judge @RibbonClientswhether there are attributes valueand sums on the annotation, and defaultConfigurationif so, register them respectively
  3. The last step is to process @RibbonClientannotations
  4. Here we can guess that RibbonClientConfigurationRegistrarthis class should be able to process these two annotations at the same time. Looking at @RibbonClientsthe source code of the annotation, we can see that it is indeed the class that was introduced.
  5. The difference between these two annotations should also be guessed, singular and double
  6. Observe the last registered code, you can see that the types of the last registered bean are all RibbonClientSpecification, pay attention here
	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());
	}
automatic assembly

After reading these codes above, we understand @RibbonClientsand @RibbonClienttwo annotations, we can still have some doubts about the overall process. Then let's take a look at what automatic assembly does.

Check the files under the Ribbon package spring.factoriesand find that a configuration class has been introduced RibbonAutoConfiguration, so let's start with this class

prerequisites
  1. @ConditionalOnClass, these classes must exist in the current environment: IClient, RestTemplate, AsyncRestTemplate,Ribbon
  2. @RibbonClients, this annotation has been mentioned just now, let's not mention it for now
  3. @AutoConfigureAfter, the load balancing must be done based on the registry, so the automatic assembly is initialized after Eureka is initialized
  4. @AutoConfigureBefore, the two classes here aside, keep it mysterious
  5. @EnableConfigurationProperties, two configuration classes, where:
    1. RibbonEagerLoadPropertiesThe class is about the properties of Ribbon's starvation loading mode
    2. ServerIntrospectorPropertiesThe class is about the properties of the secure port
assembly bean

There are quite a few classes loaded in this configuration class, but the more important ones are:

  1. SpringClientFactory, we know that each microservice will call multiple microservices, and the configuration of calling each microservice may be different, so we need this factory class to create a client load balancer, which can be used for each ribbon client A different Spring context is generated, and observing the configurationsproperties of this class also verifies this
@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, holds the SpringClientFactoryobject, of course, it has other functions, which will not be mentioned here
load balancing

Although I saw the Ribbon's automatic assembly function above, it seems that there is still some distance from the truth. This is because although the Ribbon is ready, the load balancing has not yet been seen. SpringCloud puts the automatic configuration related to load balancing in the spring-cloud-commons package. The configuration class for load balancing isLoadBalancerAutoConfiguration

Several beans registered in this class are more core

LoadBalancerInterceptor

Client request interceptor

RestTemplateCustomizer

Used to RestTemplateadd interceptors to all

@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
Load balancing core implementation

Now we can guess that the whole core should be on this interceptor, take a look at the core method of the interceptor:

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

The requestFactory.createRequest(request, body, execution)method is to encapsulate the request parameters as request. Focus on the executemethod

	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);
	}
Create a load balancer

We know that the load balancer of each Ribbon client is unique, and the first line getLoadBalancerwill create this load balancer

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

The final logic is to get it from the cache if it exists, and create it if it doesn't exist

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

RibbonClientSpecificationThe process of creating a big topic is to create several types of configurations registered by the two annotations mentioned at the beginning of the article.

Get service

getServerThe implementation of the method should be able to guess, using a specific load balancer combined with the corresponding load balancing algorithm plus service list filtering, service health detection and other operations will finally obtain an available service

call service

Here, the service is encapsulated asRibbonServer

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

In addition to these properties, RibbonServerthere is also a method

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

This method converts the service from an instance id to a callable 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);
	}

Then send the http request

1

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324227898&siteId=291194637