Ribbon - 負載均衡流程

Ribbon - 初始化中提到了,@LoadBalanced註解的RestTemplate會注入攔截器LoadBalancerInterceptor,咱們看看LoadBalancerInterceptor是怎麼作的。git

LoadBalancerInterceptor#intercept

這裏主要是經過URL把serviceId取出來,而後調用LoadBalancerClient execute方法。github

@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和端口了。spring

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,若是沒有則建立。segmentfault

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裏面。ide

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

BaseLoadBalancer#initWithConfig

這個方法主要是開啓定時任務ping,也就是檢查其餘服務是不是存活的。spa

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是判斷。.net

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+端口),再經過負載均衡策略,獲取某個服務進行遠程調用。
image

相關文章
相關標籤/搜索