关于ribbon:Ribbon-负载均衡流程

Ribbon – 初始化中提到了,@LoadBalanced注解的RestTemplate会注入拦截器LoadBalancerInterceptor,咱们看看LoadBalancerInterceptor是怎么做的。

LoadBalancerInterceptor#intercept

这里次要是通过URL把serviceId取出来,而后调用LoadBalancerClient execute办法。

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

RibbonLoadBalancerClient#execute

次要是获取ILoadBalancerServer,通过Server进行近程调用。这个server曾经是有对应的IP和端口了。

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

RibbonLoadBalancerClient#getLoadBalancer会调用SpringClientFactory#getLoadBalancer,SpringClientFactory的注入能够看上一章。SpringClientFactory#getLoadBalancer调用父类的NamedContextFactory#getInstance办法,NamedContextFactory#getInstance会判断之前是否创立了serviceId对应的AnnotationConfigApplicationContext,如果没有则创立。

NamedContextFactory#getContext

这里关注是createContext办法,他注册了RibbonEurekaAutoConfiguration和RibbonClientConfiguration,注册后就调用refresh办法。这两个值是怎么来的,能够参考上一章SpringClientFactory的创立。这两个有先后顺序,所以会先加载RibbonEurekaAutoConfiguration后加载RibbonEurekaAutoConfiguration,而IPing、ServerList都有@ConditionalOnMissingBean注解,所以优先实例化RibbonEurekaAutoConfiguration的IPing、ServerList。

protected AnnotationConfigApplicationContext getContext(String name) {
    // 如果没有,则调用createContext创立
    if (!this.contexts.containsKey(name)) {
        synchronized (this.contexts) {
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, createContext(name));
            }
        }
    }
    //返回
    return this.contexts.get(name);
}

protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    // 注册org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    // 注册org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
    context.register(PropertyPlaceholderAutoConfiguration.class,
            this.defaultConfigType);
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            this.propertySourceName,
            Collections.<String, Object>singletonMap(this.propertyName, name)));
    if (this.parent != null) {
        // Uses Environment from parent as well as beans
        context.setParent(this.parent);
        // jdk11 issue
        // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
        context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}

refresh加载比拟重要的有以下几个,代码就不贴了,这几个类就是在下面注册的RibbonEurekaAutoConfiguration和RibbonClientConfiguration外面。

  1. 加载IPing,查看服务存活(NIWSDiscoveryPing)
  2. 加载ServerList<Server>:服务实例列表,(DomainExtractingServerList)
  3. 加载IClientConfig(DefaultClientConfigImpl)
  4. 加载IRule:负载平衡规定,(ZoneAvoidanceRule)
  5. 加载ServerListUpdater,服务实例更新列表,(PollingServerListUpdater)
  6. 加载ServerListFilter<Server>,服务实例过滤列表,(ZonePreferenceServerListFilter)
  7. 加载ILoadBalancer:用于抉择server,(ZoneAwareLoadBalancer)

这几个bean加载的时候,都有相似以下的判断,这个次要是判断是否有自定义配置,如果没有,则取默认,有就取自定义的。

if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
    return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}

上面次要看ZoneAwareLoadBalancer的构造函数,他会调用父类的BaseLoadBalancer#initWithConfig办法和DynamicServerListLoadBalancer#restOfInit办法。

BaseLoadBalancer#initWithConfig

这个办法次要是开启定时工作ping,也就是查看其余服务是否是存活的。

void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
    // 其余略
    // 设置ping的工夫,默认30秒,也就是说服务挂了最多30秒就发现了
    setPingInterval(pingIntervalTime);
    // 设置最大次数
    setMaxTotalPingTime(maxTotalPingTime);
    // 设置IRule,这里会把以后的ILoadBalancer赋值给IRule
    setRule(rule);
    // 开启定时工作ping
    setPing(ping);
    // 其余略
}

定时工作的调用过程简略为:PingTask#run–>BaseLoadBalance.Pinger#runPinger–>BaseLoadBalance.SerialPingStrategy#pingServers–>NIWSDiscoveryPing#isAlive,NIWSDiscoveryPing#isAlive是判断。

public void runPinger() throws Exception {
    // 其余略
    // 通过BaseLoadBalance.SerialPingStrategy#pingServers调用NIWSDiscoveryPing#isAlive,
    // 次要是判断服务的状态是不是UP,如果是UP,就是存活。
    results = pingerStrategy.pingServers(ping, allServers);
    
    final List<Server> newUpList = new ArrayList<Server>();
    final List<Server> changedServers = new ArrayList<Server>();
    // 前面代码次要是把存活的存入到newUpList再到upServerList,把变动的存入changedServers,并调用监听告诉
    // 其余略
}

DynamicServerListLoadBalancer#restOfInit

这个办法次要有两个性能,一个是调用DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature开启定时工作获取Eureka的注册表,一个是调用DynamicServerListLoadBalancer#updateAllServerList办法获取Eureka的注册表。所以次要看看DynamicServerListLoadBalancer#updateListOfServers。

public void updateListOfServers() {
    List<T> servers = new ArrayList<T>();
    if (serverListImpl != null) {
        // 获取Eureka的注册表
        servers = serverListImpl.getUpdatedListOfServers();

        if (filter != null) {
            // 过滤zone为defaultZone
            servers = filter.getFilteredListOfServers(servers);
            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);
        }
    }
    // 赋值allServerList
    updateAllServerList(servers);
}

RibbonLoadBalancerClient#getServer

咱们回到RibbonLoadBalancerClient#execute来,getLoadBalancer办法曾经把该加载的加载了,获取到ILoadBalancer后,咱们就曾经获取到被调用的服务列表了。当初就要获取某一个服务来做近程调用了,因为注入的是ZoneAvoidanceRule,所以默认的就是轮询来获取Server。获取到Server后,就能够进行近程调用了。

private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextIndex.get();
        int next = (current + 1) % modulo;
        if (nextIndex.compareAndSet(current, next) && current < modulo)
            return current;
    }
}

总结

ribbon在调用之前,会获取到Eureka的注册信息,并开启定时工作去更新Eureka的注册信息,以及检测是否存活,默认都是30秒。调用的时候,通过serviceId,获取服务列表(此时已转为IP+端口),再通过负载平衡策略,获取某个服务进行近程调用。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理