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
如图显示的就是自动加载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被加载进来。反查找一下
ILoadBalancer一共被这几个方法所实现,但是还是不知道初始化话的时候到底是默认用的那个,所以这个应该在XXConfiguration类中被@Bean标注,搜出了这几个结果,只有这个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);
}
这里默认就返回了一个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/hello 到http://127.0.0.1:8080/hello的地址转换,并发送http请求