Ribbon源码--执行流程(1)

104 阅读5分钟

Ribbon源码--执行流程(1)

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

切入点

Ribbon的使用主要是基于@LoadBalanced注解,所以@LoadBalanced作为ribbon入口开始阅读

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

}

看到这里其实就已经没什么思绪了,就是一个简单的注解,但是当我进行反差找的时候发现有一个LoadBalancerAutoConfiguration类中引用了@LoadBalanced,通常情况下XXAutoConfiguration这样的明明方式都是某某的配置类,基于这个明明习惯查看LoadBalancerAutoConfiguration的类

LoadBalancerAutoConfiguration

image.png

如图显示的就是自动加载ribbon的配置

在自动加载里可以看到@ConditionalOnClass这个注解,这个注解说明必须有标注的类存在当前的类才会进行bean的注入,先忽略继续看这个AutoConfiguration里都有些什么

首先是一个SmartInitializingSingleton,这个bean就是自定义的一些属性。而且是在spring bean实例化之后调用的。

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
    final List<RestTemplateCustomizer> customizers) {
    return new SmartInitializingSingleton() {
        @Override
        public void afterSingletonsInstantiated() {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        }
    };
}

其次是一个LoadBalancerRequestFactory方法,这个是返回了一个LoadBalancer的工厂对象

@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
    LoadBalancerClient loadBalancerClient) {
    return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}

在后面就是一个LoadBalancerInterceptorConfig ,看名字说的是LoadBalancer的拦截器配置

@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

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

种种包含了LoadBalancerInterceptor和RestTemplateCustomizer两个bean的注入,还是看名字RestTemplateCustomizer这个应该是自定义的东西,而Interceptor貌似更加的重要,而这个AutoConfiguration类中剩下的方法都是和重试的配置相关,所以不再往下看。

找到这个Interceptor点进去看一下

LoadBalancerInterceptor

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

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

可以想象一下,@LoadBalance注解是作用在RestTemplate上的,那么就是说在执行http请求的时候实际上会被AOP拦截,执行AOP的逻辑,然后在执行http的请求,而且刚好这里有个this.loadBalancer.execute方法,所以后续我在这个地方打了一个断点,确实是进入到这里了。这也说明我想的没有错,符合AOP的原理。。。。

但是在调用execute前还有两行代码

//这里获得了一个原始的url http://serviceName/hello
final URI originalUri = request.getURI();
//这里获得了服务名称  serviceName
String serviceName = originalUri.getHost();

这里执行完后就会进入RibbonLoadBalancerClient类中

RibbonLoadBalancerClient

interceptor拦截后会执行execute方法

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

进来会先根据服务名获取到一个负载均衡器,这个负载均衡器会从SpringClientFactory中获取,它是一个什么意思呢?就是说从SpringClientFactory中获取的时候实际上是从一个

Map<String, AnnotationConfigApplicationContext> 的对象里先拿到一个AnnotationConfigApplicationContext,这也就意味着每个服务都会有一个对应的AnnotationConfigApplicationContext,而这个东西是spring的一个容器,简单的说就是一个server对应一个容器,每次获取负载均衡器都需要从自己的容器中获取。这也就能理解ribbon可以针对不同的服务设置不同的策略了。

protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

看到这里其实还是不知道它拿了个什么负载均衡器

所以继续看,它返回了一个ILoadBalancer,这个东西如果可以从ApplicationContext中获取到的话,那么一定会在Spring初始化的时候当做bean被加载进来。反查找一下

image.png

ILoadBalancer一共被这几个方法所实现,但是还是不知道初始化话的时候到底是默认用的那个,所以这个应该在XXConfiguration类中被@Bean标注,搜出了这几个结果,只有这个RibbonClientConfiguration长得比较像

image.png

点进去看了一下

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

这里默认就返回了一个ZoneAwareLoadBalancer

后续拿到ILoadBalance后会执行一个getServer,这个方法看起来就像是在根据负载均衡的策略进行摇ip

根据默认的ILoadBalance 这个getServer方法最终会走到ZoneAwareLoadBalancer.chooseServer

public Server chooseServer(Object key) {
        //默认应该会走到这个if里面去 后去的看样子像是多机房分布时候才会去调用
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }

继续查看chooseServer方法,里面得IRule方法就是默认的负载均衡策略RoundRobinRule()

然后会在RoundRobinRule()中选择一个需要调用的server

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        //这里的负载均衡策略是轮询
        while (server == null && count++ < 10) {
            //获得状态为up的服务器
            List<Server> reachableServers = lb.getReachableServers();
            //获得所有的服务器
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();

            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
			//这里会根据上次请求的(服务器循环计数器 + 1)取模计算出当前要访问的机器
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
			//判断选中的服务器是否存活
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }
		//如果选了超过10次服务器都是不可用的那么就抛出异常
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

返回选中的server,然后会构造一个RibbonServer,完事之后就会执行execute

	public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
        //这里传进来的就是RibbonServer,所以这里一定会进来
		if(serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer)serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		//这里会再次从spring容器中获取到一个RibbonLoadBalancerContext的bean
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
            //这里就是实际调用
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		// catch IOException and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}

apply这个方法里面会对传入的server发起一个指定的请求,这里其实感觉上应该是要替换实际的url地址,但是并没有发现,只是在ServiceRequestWrapper中发现重写了HttpRequest的getURI(),而这个里面正是替换真正的url的地方,继续执行excute方法,会进入到Spring web的代码中,在这个里面请求之前调用getURI()的方法完成了真正的替换,到此可以说ribbon的请求执行流程已经结束,至于返回结果之类的可以不需要关系是什么样的流程了。

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
			final byte[] body, final ClientHttpRequestExecution execution) {
		return new LoadBalancerRequest<ClientHttpResponse>() {

			@Override
			public ClientHttpResponse apply(final ServiceInstance instance)
					throws Exception {
				HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
				if (transformers != null) {
					for (LoadBalancerRequestTransformer transformer : transformers) {
						serviceRequest = transformer.transformRequest(serviceRequest, instance);
					}
				}
				return execution.execute(serviceRequest, body);
			}

		};
	}

总结

总结一下Ribbon的执行流程:

1、当发起http请求的时候,会被@LoadBalanceInterceptor拦截

2、拦截后会先获取到对应服务的负载均衡器

3、从负载均衡其中获取到具体的负载均衡策略,并通过该策略获取到具体可用的server地址

4、执行execute的apply方法,最终执行到spring web的ClientHttpRequestExecution类中的execute方法,并在方法中调用getURI()实现url从http://serverA/hellohttp://127.0.0.1:8080/hello的地址转换,并发送http请求