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
RibbonAutoConfiguration
@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");
}
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
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
El método getContext llama al método createContext
org.springframework.cloud.context.named.NamedContextFactory#createContext
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
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.
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
RestTemplate hereda InterceptingHttpAccessor, por lo que tiene la capacidad de obtener y establecer interceptores.
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
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
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
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方法
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
y al método updateListOfServers.
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
//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");
}
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