Ribbon源码深度刨析-(4)服务列表更新与负载均衡

本文详细剖析了Ribbon如何与Eureka进行整合,通过DynamicServerListLoadBalancer组件的enableAndInitLearnNewServersFeature方法,定时从Eureka客户端获取服务注册表。PollingServerListUpdater作为定时更新的组件,使用ScheduledFuture每30秒更新服务器列表。负载均衡策略中,ZoneAwareLoadBalancer的chooseServer方法选择服务器,采用ZoneAvoidanceRule进行轮询分配,确保服务的均匀分布。整个流程清晰明了,展现了Ribbon在服务发现和负载均衡中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

“不积跬步,无以至千里。”

之前我们看到了ribbon负载均衡组件里的服务列表实际上是从eureka client自己本地的缓存中获取的

eureka client自己本身,是不断的去从eureka server每隔30秒更新一次注册表,拉取增量注册表

所以ribbon和eureka整合的机制里,肯定得有一个组件,负责每隔一定的时间,从本地的eureka client里刷新一下服务的注册表到LoadBalancer中

继续回到 DynamicServerListLoadBalancer 组件的 restOfInit() 方法

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
    this.setEnablePrimingConnections(false);
    enableAndInitLearnNewServersFeature();

    updateListOfServers();
    if (primeConnection && this.getPrimeConnections() != null) {
        this.getPrimeConnections()
            .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}

enableAndInitLearnNewServersFeature();

这一行代码比较关键,实际上就包含了定时去拉取eureka client本地注册表的逻辑,不妨进去看看

/**
 * Feature that lets us add new instances (from AMIs) to the list of
 * existing servers that the LB will use Call this method if you want this
 * feature enabled
 */
public void enableAndInitLearnNewServersFeature() {
    LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
    serverListUpdater.start(updateAction);
}

先看这个注释,大概就知道点意思了,允许我们将新实例添加到已存在的servers… …

这里调用了 serverListUpdaterd 的start方法

这个 serverListUpdaterd 实际上就是一个 PollingServerListUpdater的实例Bean,同样在 RibbonClientConfiguration 这个配置类中自动配置的

@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
    return new PollingServerListUpdater(config);
}

start方法传入了一个 updateAction

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
    @Override
    public void doUpdate() {
        updateListOfServers();
    }
};

这个action里面有一个 doUpdate() 方法,调用了 updateListOfServers()

这个方法正是上一篇文章分析的,ribbon从eureka client那里获取server list的方法!!!

到这里,我们可以大胆猜想,这个所谓的 enableAndInitLearnNewServersFeature ,

很可能就是通过 PollingServerListUpdater 这个组件定时去eureka client拉取本地的服务注册表到ribbon的负载均衡组件,那么一旦有服务实例上线或者故障,eureka client就会通过定时拉取增量注册表的机制更新本地缓存,进而ribbon的负载均衡组件里就是最新的服务实例了

现在只需要看看 PollingServerListUpdater 的start方法就可以验证我们的猜想

 @Override
public synchronized void start(final UpdateAction updateAction) {
    if (isActive.compareAndSet(false, true)) {
        final Runnable wrapperRunnable = new Runnable() {
            @Override
            public void run() {
                if (!isActive.get()) {
                    if (scheduledFuture != null) {
                        scheduledFuture.cancel(true);
                    }
                    return;
                }
                try {
                    updateAction.doUpdate();
                    lastUpdated = System.currentTimeMillis();
                } catch (Exception e) {
                    logger.warn("Failed one update cycle", e);
                }
            }
        };

        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
            wrapperRunnable,
            initialDelayMs,
            refreshIntervalMs,
            TimeUnit.MILLISECONDS
        );
    } else {
        logger.info("Already active, no-op");
    }
}

updateAction.doUpdate();

这里会把上面的方法封装到一个 wrapperRunnable 的线程里,然后通过一个 scheduledFuture 调度器定时执行,果然我们又猜对了

initialDelayMs,第一次执行的延迟,默认1s

refreshIntervalMs,后续的执行间隔时间,默认30s

private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;

ok,到这里,已经很明确了,定时更新ribbon负载均衡组件里的server list是通过 enableAndInitLearnNewServersFeature 方法完成的,是通过 PollingServerListUpdater 组件内部的ScheduledFuture定时调用 updateListOfServers() 实现的

接着,用剩下的篇幅说说负载均衡吧,这个也是挺重要的

Server server = getServer(loadBalancer);

protected Server getServer(ILoadBalancer loadBalancer) {
    if (loadBalancer == null) {
        return null;
    }
    return loadBalancer.chooseServer("default"); // TODO: better handling of key
}

loadBalancer.chooseServer(“default”);

可以看到,调用得是 ZoneAwareLoadBalancerchooseServer 方法

@Override
public Server chooseServer(Object key) {
    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;
    ... ...
}

这个方法,真正会调用就上面的super.chooseServer(key);

下面的涉及到多机房的概念,我就给省略了,因为我们这里暂时没有Zone(机房)的概念

如果是多机房的话,在这里是可以感知到多机房的,将一个机房里的请求,转发给自己这个机房里部署的其他的服务实例

除非是一些大公司,或者是基于阿里云的环境,否则一般都是单机房部署的,上了一些规模了,有些中小型的公司系统比较成熟了,也会做双机房的环境,所以在这里我们不需要关注

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}

return rule.choose(key);

这个rule是一个ZoneAvoidanceRule类型的Bean,是在RibbonClientConfiguraiton中自动配置的

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
    if (this.propertiesFactory.isSet(IRule.class, name)) {
        return this.propertiesFactory.get(IRule.class, config, name);
    }
    ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
    rule.initWithNiwsConfig(config);
    return rule;
}

调用了他的choose()方法来选择一个server,其实是用的父类,PredicateBasedRule.choose() 方法

@Override
public Server choose(Object key) {
    ILoadBalancer lb = getLoadBalancer();
    Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    if (server.isPresent()) {
        return server.get();
    } else {
        return null;
    }       
}

List eligible = getEligibleServers(servers, loadBalancerKey);

先执行过滤规则,过滤掉一批server,根据你自己指定的filter规则,然后用round robin轮询算法,依次获取下一个server

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
    List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
    if (eligible.size() == 0) {
        return Optional.absent();
    }
    return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}

incrementAndGetModulo(eligible.size())

这个代码,是轮询的方法从server list中挑选一个index返回,即挑选一个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;
    }
}

int current = nextIndex.get();

获取当前的index,比如是0

int next = (current + 1) % modulo;

把当前的index+1,和server总数取模,假如总数是2,0+1%2,1

nextIndex.compareAndSet(current, next)

把next设置到nextIndex中,那么下次来就是1,也就是说,本次总第一台机器,下次就走第二台,依次循环

核心算法,round robin轮询算法。

到这里位置,ribbon的整合eureka和choose server已经写完了,可以发现,整体上来看,还是比较简单的,从架构设计到具体的代码实现,核心流程也是比较的清晰

最后,附上一张ribbon处理流程图(手绘)。

ribbon处理流程图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值