Netflix Ribbon - IPing机制检查服务实例是否存活

191 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


LoadBalancerInterceptor

@LoadBalanced

spring.factories

LoadBalancerAutoConfiguration

LoadBalancerInterceptor & ClientHttpRequestInterceptor

LoadBalancerClient & RibbonLoadBalancedClient

LoadBalancerRequest & LoadBalancerRequestFactory

LocalBalancerClient

spring.factories

RibbonAutoConfiguration

SpringClientFactory

RibbonLoadBalancerClient & LoadBalancerClient & LoadBalancerChooser

LocalBalancer

SpringClientFactory & NamedContextFactory

RibbonClientConfiguration

IConfig & IRule & IPing & ServerList & ServerListUpdater & ServerListFilter

ZoneAwareLoadBalancer & DynamicServerListLoadBalancer & BaseLoadBalancer & ILoadBalancer

ServerList

EurekaRibbonConfiguration

DomainExtractingServerList(DiscoveryEnableNIWSServerList) & ServerList

ServerListUpdater

PollingServerListUpdater

IRule

ZoneAvoidanceRule & PredicateBasedRule & ClientConfigEnableRoundRibbonRule & AbstractLoadBalancerRule & IRule

IPing

RibbonEurekaAutoConfiguration

@RibbonClients

EurekaRibbonClientConfiguration

NIWSDiscoveryPing & AbstractLoadBalancerPing & IPing

LocalBalancerRequest

LocalBalancerAutoConfiguration

LocalBalancerClient & List

LocalBalancerRequestFactory

ServiceRequestWrapper

ClientHttpRequestFactory(uri,method)

ClientHttpRequest

LocalBalancerRequestFactory

使用装饰器模式用ServiceInstance和LoadBalancerClient装饰HttpRequest构造成ServiceRequestWrapper

ServiceRequestWrapper

重载HttpRequest.getRUI()方法,通过ServiceInstance和LoadBalancerClient获取重构之后的真实HTTP请求地址。

ClientHttpRequestFactory

HttpAccesor & InterceptingHttpAccessor

ClientHttpRequestFactory

IPing机制检查服务实例是否存活

原生的ribbon有一个ping机制,IPing的组件,会定时的ping一下服务器,看看服务器是否存活,让自己仅仅请求存活的服务器即可。

Spring Cloud整合Ribbon,IPing组件策略。

Spring Cloud整合Ribbon环境下,使用的是DummyPing组件。

// 
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
   if (this.propertiesFactory.isSet(IPing.class, name)) {
      return this.propertiesFactory.get(IPing.class, config, name);
   }
   return new DummyPing();
}

Spring Cloud整合Ribbon环境下, 默认的情况下,IPing组件是不生效的。


package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;

public class DummyPing extends AbstractLoadBalancerPing {

    public DummyPing() {
    }

    public boolean isAlive(Server server) {
        return true;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

Ribbon整合Eureka,IPing主键策略

Ribbon整合Eureka环境下,使用的是Eureka项目中定义的NIWSDiscoveryPing(IPing)组件。

@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
   if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
      return this.propertiesFactory.get(IPing.class, config, serviceId);
   }
   NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
   ping.initWithNiwsConfig(config);
   return ping;
}

NIWSDiscoveryPing(IPing)组件,将server list中的每个server,都检查一下对应的Eureka中的InstanceInfo的状态,看看InstanceInfo,服务实例的status是否是正常的。

在ZoneAwareLoadBalancer实例构造的时候,启动一个定时调度的任务,用IPing组件对server list中的每个server都执行一下isAlive()方法。

public boolean isAlive(Server server) {
    boolean isAlive = true;
    if (server!=null && server instanceof DiscoveryEnabledServer){
           DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;                
           InstanceInfo instanceInfo = dServer.getInstanceInfo();
           if (instanceInfo!=null){                    
               InstanceStatus status = instanceInfo.getStatus();
               if (status!=null){
                   isAlive = status.equals(InstanceStatus.UP);
               }
           }
       }
    return isAlive;
}

BaseLoadBalancer.initWithConfig()方法中,setPingInterval()方法,setupPingTask()方法,启动定时ping server list的定时调度任务

// ZoneAwareLoadBalancer -> DynamicServerListLoadBalancer -> BaseLoadBalancer

public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
    initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
    this.config = clientConfig;
    String clientName = clientConfig.getClientName();
    this.name = clientName;
    int pingIntervalTime = Integer.parseInt(""
            + clientConfig.getProperty(
                    CommonClientConfigKey.NFLoadBalancerPingInterval,
                    Integer.parseInt("30")));
    int maxTotalPingTime = Integer.parseInt(""
            + clientConfig.getProperty(
                    CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
                    Integer.parseInt("2")));

    setPingInterval(pingIntervalTime);
    setMaxTotalPingTime(maxTotalPingTime);

    // cross associate with each other
    // i.e. Rule,Ping meet your container LB
    // LB, these are your Ping and Rule guys ...
    setRule(rule);
    setPing(ping);

    setLoadBalancerStats(stats);
    rule.setLoadBalancer(this);
    if (ping instanceof AbstractLoadBalancerPing) {
        ((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
    }
    logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
    boolean enablePrimeConnections = clientConfig.get(
            CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);

    if (enablePrimeConnections) {
        this.setEnablePrimingConnections(true);
        PrimeConnections primeConnections = new PrimeConnections(
                this.getName(), clientConfig);
        this.setPrimeConnections(primeConnections);
    }
    init();

}
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);
    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
    forceQuickPing();
}
class PingTask extends TimerTask {
    public void run() {
        try {
           new Pinger(pingStrategy).runPinger();
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Error pinging", name, e);
        }
    }
}
public void runPinger() throws Exception {
    if (!pingInProgress.compareAndSet(false, true)) { 
        return; // Ping in progress - nothing to do
    }
    
    // we are "in" - we get to Ping

    Server[] allServers = null;
    boolean[] results = null;

    Lock allLock = null;
    Lock upLock = null;

    try {
        /*
         * The readLock should be free unless an addServer operation is
         * going on...
         */
        allLock = allServerLock.readLock();
        allLock.lock();
        allServers = allServerList.toArray(new Server[allServerList.size()]);
        allLock.unlock();

        int numCandidates = allServers.length;
        results = pingerStrategy.pingServers(ping, allServers);

        final List<Server> newUpList = new ArrayList<Server>();
        final List<Server> changedServers = new ArrayList<Server>();

        for (int i = 0; i &lt; numCandidates; i++) {
            boolean isAlive = results[i];
            Server svr = allServers[i];
            boolean oldIsAlive = svr.isAlive();

            svr.setAlive(isAlive);

            if (oldIsAlive != isAlive) {
                changedServers.add(svr);
                logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}", 
                  name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
            }

            if (isAlive) {
                newUpList.add(svr);
            }
        }
        upLock = upServerLock.writeLock();
        upLock.lock();
        upServerList = newUpList;
        upLock.unlock();

        notifyServerStatusChangeListener(changedServers);
    } finally {
        pingInProgress.set(false);
    }
}

默认每隔30秒执行一下PingTask调度任务,执行一个Pinger()的东西,执行SerialPingStrategy。

命令模式+策略模式

用IPing组件对每个server都执行一下isAlive()方法。

Spring Cloud 整合 Ribbon 整合 Eureka

Eureka项目下的EurekaRibbonClinetConfiguration内实例化IPing组件,Ribbon项目下的RibbonClientConfiguration内IPing组件的实例化由于@ConfigurationOnMissingBean注解的原因不会执行实例化。