In-depth understanding of the ribbon source code

Ribbon source code analysis

automatic assembly

rely

<!--添加ribbon的依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

spring-cloud-starter-netflix-ribbon
See the starter component, we can go to the spring.factories file of the dependent package to see
insert image description here
RibbonAutoConfiguration
insert image description here

@AutoConfigurationBefore
indicates that the current configuration class is loaded before a certain configuration class is loaded.
Explain that RibbonAutoConfiguration should be loaded before LoadBalancerAutoConfiguration

Take a look at the class injected for us

@Bean
	public SpringClientFactory springClientFactory() {
    
    
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}

new SpringClientFactory()

	public SpringClientFactory() {
    
    
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}

insert image description here

Let's look at the parent class: NamedContextFactory

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
    
    
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

Pass RibbonClientConfiguration as a parameter to the parent class

Let's see how RibbonClientConfiguration is parsed into bean-defined
insert image description here
RibbonApplicationContextInitializer to implement ApplicationListener, so
springboot will call the onApplicationEvent method when it starts

protected void initialize() {
    
    
		if (clientNames != null) {
    
    
			for (String clientName : clientNames) {
    
    
				this.springClientFactory.getContext(clientName);
			}
		}
	}

	@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
    
    
		initialize();
	}

Let's look at the getContext method
org.springframework.cloud.context.named.NamedContextFactory#getContext

insert image description here
The getContext method calls the createContext method
org.springframework.cloud.context.named.NamedContextFactory#createContext
insert image description here
This defaultConfigType is the RibbonClientConfiguration we passed in

Let's look at the register method

public void register(Class<?>... annotatedClasses) {
    
    
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}

org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register方法

public void register(Class<?>... annotatedClasses) {
    
    
		for (Class<?> annotatedClass : annotatedClasses) {
    
    
			registerBean(annotatedClass);
		}
	}

Seeing this is not the way our spring parses the configuration class, it is to parse the configuration class into a bean definition

Why do we have to say so much about the RibbonClientConfiguration class, because this class injects many core classes for us

LoadBalancerAutoConfiguration

insert image description here

You can see that the condition for LoadBalancerAutoConfiguration to take effect is that there must be a LoadBalancerClient class in the container.
This class will be injected into RibbonAutoConfiguration

insert image description here

When collecting the RestTemplate represented by @LoadBalanced,
spring does it for us.
We can look at the @LoadBalance annotation

@Target({
    
    ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
    
    
}

You can see that @LoadBalance is a composite annotation, and the bottom layer still uses the @Qualifier annotation,
so the RestTemplate collection can be collected

RestTemplate
insert image description here
RestTemplate inherits InterceptingHttpAccessor so it has the ability to get and set interceptors

insert image description here

loadBalancedRestTemplateInitializer

loadBalancedRestTemplateInitializer() initializes some things, there is an internal class in it, get the current RestTemplate for traversal, traversal customizers are specially used to customize RestTemplate components, and use each customizer to customize each RestTemplate. In this method, all annotated by
@LoadBalanced The identified RestTemplate adds the ribbon's custom interceptor LoadBalancerInterceptor

@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    
    
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    
    
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    
    
				for (RestTemplateCustomizer customizer : customizers) {
    
    
					customizer.customize(restTemplate);
				}
			}
		});
	}

In the restTemplateCustomizer method, a ClientHttpRequestInterceptor interceptor will be customized for each RestTemplate put in, and the interceptor is implemented by its subclass LoadBalancerInterceptor

Create Ribbon custom interceptor LoadBalancerInterceptor

	@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
    
    
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

Add interceptor specific method.
First get the current interceptor collection (List), then add loadBalancerInterceptor to the current collection, and finally put the new collection back into the restTemplate.

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

LoadBalancerInterceptor
will enter the intercept method
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept when calling an api with resttemplate

	@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);
				//当前loadBalancer为RibbonLoadBalancerClient
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

When the request is initiated, the ribbon will use the LoadBalancerInterceptor interceptor to intercept it. In this interceptor, the LoadBalancerClient.execute() method will be called.
Execute() is executed by the subclass RibbonLoadBlancerClient

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(j

@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
			throws IOException {
    
    
		return execute(serviceId, request, null);
	}

	
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
    
    
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		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);
	}
	

ILoadBalancer is injected
through the RibbonClientConfiguration configuration class

@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    
    
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
    
    
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

Specifically return ZoneAwareLoadBalancer

ZoneAwareLoadBalancer
insert image description here

   public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
    
    
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

Look at the parent class DynamicServerListLoadBalancer

 public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
    
    
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
    
    
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        restOfInit(clientConfig);
    }

The parent class BaseLoadBalancer of DynamicServerListLoadBalancer

  public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
    
    
        initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
    }

Look at the initWithConfig method
insert image description here

com.netflix.loadbalancer.BaseLoadBalancer#setPingInterval

public void setPingInterval(int pingIntervalSeconds) {
    
    
        if (pingIntervalSeconds < 1) {
    
    
            return;
        }

        this.pingIntervalSeconds = pingIntervalSeconds;
        if (logger.isDebugEnabled()) {
    
    
            logger.debug("LoadBalancer [{}]:  pingIntervalSeconds set to {}",
        	    name, this.pingIntervalSeconds);
        }
        setupPingTask(); // since ping data changed
    }
    
       void setupPingTask() {
    
    
        if (canSkipPing()) {
    
    
            return;
        }
        if (lbTimer != null) {
    
    
            lbTimer.cancel();
        }
        lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
                true);
                //启动一个任务调度,每隔10秒 ping一次服务实例,检查状态,
        lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
        forceQuickPing();
    }

This step is mainly to start a task scheduler, ping the service instance every 10 seconds, and check the status

restOfInit
insert image description here

  void restOfInit(IClientConfig clientConfig) {
    
    
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        enableAndInitLearnNewServersFeature();

        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
    
    
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }



  public void enableAndInitLearnNewServersFeature() {
    
    
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);
    }

enableAndInitLearnNewServersFeature方法

insert image description here

enableAndInitLearnNewServersFeature(): Regular update
In PollingServerListUpdater, a Runnable thread is created, which is the behavior of executing UpdateAction. After a certain period of delay, execute the Runnable thread at regular intervals, and then execute the operations in UpdateAction Refresh the registry,
the default is that after 1 second, the Runnable thread will be executed for the first time,
//after that, the Runnable thread will be executed every 30 seconds,] t refresh the registry to the LoadBalancer of your own ribbon

Take a look at the doupdate method
insert image description here
and the updateListOfServers method.
insert image description here
The registry center here takes nacos as an example
com.alibaba.cloud.nacos.ribbon.NacosServerList#getUpdatedListOfServers

  public List<NacosServer> getUpdatedListOfServers() {
    
    
        return this.getServers();
    }

    private List<NacosServer> getServers() {
    
    
        try {
    
    
            String group = this.discoveryProperties.getGroup();
            List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, group, true);
            return this.instancesToServerList(instances);
        } catch (Exception var3) {
    
    
            throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + this.serviceId, var3);
        }
    }

看下selectInstances 方法
com.alibaba.nacos.client.naming.NacosNamingService#selectInstances(java.lang.String, java.lang.String, boolean)

public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
    
    
        return this.selectInstances(serviceName, groupName, healthy, true);
    }



   public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
    
    
        ServiceInfo serviceInfo;
        if (subscribe) {
    
    
            serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
    
    
            serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        }

        return this.selectInstances(serviceInfo, healthy);
    }

The focus is on the getServiceInfo method

 public ServiceInfo getServiceInfo(String serviceName, String clusters) {
    
    
        LogUtils.NAMING_LOGGER.debug("failover-mode: " + this.failoverReactor.isFailoverSwitch());
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (this.failoverReactor.isFailoverSwitch()) {
    
    
            return this.failoverReactor.getService(key);
        } else {
    
    
        	//从缓存中获取实例信息
            ServiceInfo serviceObj = this.getServiceInfo0(serviceName, clusters);
            //如果不存在则去nacos服务端调用
            if (null == serviceObj) {
    
    
                serviceObj = new ServiceInfo(serviceName, clusters);
                this.serviceInfoMap.put(serviceObj.getKey(), serviceObj);
                this.updatingMap.put(serviceName, new Object());
                //nacos服务端调用
                this.updateServiceNow(serviceName, clusters);
                this.updatingMap.remove(serviceName);
            } else if (this.updatingMap.containsKey(serviceName)) {
    
    
                synchronized(serviceObj) {
    
    
                    try {
    
    
                        serviceObj.wait(5000L);
                    } catch (InterruptedException var8) {
    
    
                        LogUtils.NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, var8);
                    }
                }
            }
			
			//异步更新内存注册列表
            this.scheduleUpdateIfAbsent(serviceName, clusters);
            return (ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey());
        }
    }

The timed task here is basically over

Then go on to the next process and go back to the place where it was originally intercepted
insert image description here
//Select an instance through the load balancing algorithm to call it later

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    
    
		if (loadBalancer == null) {
    
    
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

insert image description here
com.netflix.loadbalancer.BaseLoadBalancer#chooseServer

public Server chooseServer(Object key) {
    
    
        if (counter == null) {
    
    
            counter = createCounter();
        }
        counter.increment();
        //rule就是Irule
        if (rule == null) {
    
    
            return null;
        } else {
    
    
            try {
    
    
                return rule.choose(key);
            } catch (Exception e) {
    
    
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

Selection of service strength through Irue

Let's look at the Irule implementation class NacosRule

com.alibaba.cloud.nacos.ribbon.NacosRule#choose

 public Server choose(Object key) {
    
    
        try {
    
    
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
            String name = loadBalancer.getName();
            NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
            //这个和定时任务调用的一样
            List<Instance> instances = namingService.selectInstances(name, true);
            if (CollectionUtils.isEmpty(instances)) {
    
    
                LOGGER.warn("no instance in service {}", name);
                return null;
            } else {
    
    
                List<Instance> instancesToChoose = instances;
                if (StringUtils.isNotBlank(clusterName)) {
    
    
                    List<Instance> sameClusterInstances = (List)instances.stream().filter((instancex) -> {
    
    
                        return Objects.equals(clusterName, instancex.getClusterName());
                    }).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(sameClusterInstances)) {
    
    
                        instancesToChoose = sameClusterInstances;
                    } else {
    
    
                        LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{
    
    name, clusterName, instances});
                    }
                }

				//根据负载均衡策略选取一个
                Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
                return new NacosServer(instance);
            }
        } catch (Exception var9) {
    
    
            LOGGER.warn("NacosRule error", var9);
            return null;
        }
    }

This method is actually to obtain the service instance from the local ring, if it is obtained, it will be returned, if it is
not obtained, it will initiate a remote call to the nacos server to obtain the service instance set,
and finally select an instance node through the load balancing algorithm for subsequent calls

Finally, replace the ip and port in the interceptor method and initiate a remote http call

Guess you like

Origin blog.csdn.net/qq_42600094/article/details/130683321