Comprensión profunda del código fuente de la cinta

Análisis del código fuente de la cinta

montaje automático

confiar

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

spring-cloud-starter-netflix-ribbon
Vea el componente de inicio, podemos ir al archivo spring.factories del paquete dependiente para ver
inserte la descripción de la imagen aquí
RibbonAutoConfiguration
inserte la descripción de la imagen aquí

@AutoConfigurationBefore
indica que la clase de configuración actual se carga antes de que se cargue una determinada clase de configuración.
Explique que RibbonAutoConfiguration debe cargarse antes que LoadBalancerAutoConfiguration

Echa un vistazo a la clase inyectada para nosotros.

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

nueva SpringClientFactory()

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

inserte la descripción de la imagen aquí

Veamos la clase padre: NamedContextFactory

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

Pase RibbonClientConfiguration como parámetro a la clase principal

Veamos cómo se analiza RibbonClientConfiguration en
inserte la descripción de la imagen aquí
RibbonApplicationContextInitializer definido por bean para implementar ApplicationListener, por lo que
springboot llamará al método onApplicationEvent cuando se inicie

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

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

Veamos el método getContext
org.springframework.cloud.context.named.NamedContextFactory#getContext

inserte la descripción de la imagen aquí
El método getContext llama al método createContext
org.springframework.cloud.context.named.NamedContextFactory#createContext
inserte la descripción de la imagen aquí
Este defaultConfigType es el RibbonClientConfiguration que pasamos

Veamos el método de registro.

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

Ver que esta no es la forma en que nuestro resorte analiza la clase de configuración, es analizar la clase de configuración en una definición de bean

¿Por qué tenemos que decir tanto sobre la clase RibbonClientConfiguration , porque esta clase inyecta muchas clases principales para nosotros?

LoadBalancerConfiguración automática

inserte la descripción de la imagen aquí

Puede ver que la condición para que LoadBalancerAutoConfiguration surta efecto es que debe haber una clase LoadBalancerClient en el contenedor.
Esta clase se inyectará en RibbonAutoConfiguration.

inserte la descripción de la imagen aquí

Al recopilar el RestTemplate representado por @LoadBalanced,
Spring lo hace por nosotros.
Podemos ver la anotación @LoadBalance

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

Puede ver que @LoadBalance es una anotación compuesta, y la capa inferior todavía usa la anotación @Qualifier,
por lo que se puede recopilar la colección RestTemplate

RestTemplate
inserte la descripción de la imagen aquí
RestTemplate hereda InterceptingHttpAccessor, por lo que tiene la capacidad de obtener y establecer interceptores.

inserte la descripción de la imagen aquí

loadBalancedRestTemplateInitializer

loadBalancedRestTemplateInitializer() inicializa algunas cosas, hay una clase interna en él, obtiene el RestTemplate actual para el recorrido, los personalizadores transversales se usan especialmente para personalizar los componentes de RestTemplate, y usa cada personalizador para personalizar cada RestTemplate. En este método, todo anotado por
@LoadBalanced El RestTemplate identificado agrega el interceptor personalizado de la cinta 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);
				}
			}
		});
	}

En el método restTemplateCustomizer, se personalizará un interceptor ClientHttpRequestInterceptor para cada RestTemplate colocado, y el interceptor se implementará mediante su subclase LoadBalancerInterceptor.

Crear un interceptor personalizado de cinta LoadBalancerInterceptor

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

Agregar método específico del interceptor.
Primero, obtenga la colección de interceptores actual (Lista), luego agregue loadBalancerInterceptor a la colección actual y, finalmente, vuelva a colocar la nueva colección en el 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
ingresará el método de intercepción
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept al llamar a una API con 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));
	}

Cuando se inicia la solicitud, la cinta utilizará el interceptor LoadBalancerInterceptor para interceptarla. En este interceptor se llamará al método LoadBalancerClient.execute()
Execute() es ejecutado por la subclase 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 se inyecta
a través de la clase de configuración RibbonClientConfiguration

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

Específicamente devolver ZoneAwareLoadBalancer

Equilibrador de carga ZoneAware
inserte la descripción de la imagen aquí

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

Mire la clase principal 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);
    }

La clase padre BaseLoadBalancer de DynamicServerListLoadBalancer

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

Mire el método initWithConfig
inserte la descripción de la imagen aquí

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

Este paso es principalmente para iniciar un programador de tareas, hacer ping a la instancia de servicio cada 10 segundos y verificar el estado

restOfInit
inserte la descripción de la imagen aquí

  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方法

inserte la descripción de la imagen aquí

enableAndInitLearnNewServersFeature(): Actualización periódica
En PollingServerListUpdater, se crea un subproceso Runnable, que es el comportamiento de ejecutar UpdateAction.Después de un cierto período de retraso, ejecute el subproceso Runnable a intervalos regulares y luego ejecute las operaciones en UpdateAction Actualizar el registro,
el el valor predeterminado es que después de 1 segundo, el subproceso Runnable se ejecutará por primera vez,
//después de eso, el subproceso Runnable se ejecutará cada 30 segundos,] t actualice el registro al LoadBalancer de su propia cinta

Eche un vistazo al método doupdate
inserte la descripción de la imagen aquí
y al método updateListOfServers.
inserte la descripción de la imagen aquí
El centro de registro aquí toma nacos como ejemplo
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);
    }

El foco está en el método getServiceInfo

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

La tarea cronometrada aquí básicamente ha terminado.

Luego pase al siguiente proceso y regrese al lugar donde fue interceptado originalmente
inserte la descripción de la imagen aquí
//Seleccione una instancia a través del algoritmo de balanceo de carga para llamarla más tarde

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

inserte la descripción de la imagen aquí
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;
            }
        }
    }

Selección de fuerza de servicio a través de Irue

Veamos la clase de implementación de Irule NacosRule

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

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

Este método es en realidad para obtener la instancia de servicio del anillo local. Si se obtiene, se devolverá. Si no se obtiene,
iniciará una llamada remota al servidor de nacos para obtener el conjunto de instancias de servicio.
Finalmente, un el nodo de instancia se selecciona a través del algoritmo de equilibrio de carga para las llamadas posteriores.

Finalmente, reemplace la ip y el puerto en el método interceptor e inicie una llamada http remota

Supongo que te gusta

Origin blog.csdn.net/qq_42600094/article/details/130683321
Recomendado
Clasificación