图解+源码讲解 Ribbon 服务选择原理

899 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

图解+源码讲解 Ribbon 服务选择原理

遇到困难时不要抱怨,既然改变不了过去,那么就努力改变未来 ribbon 相关文章
图解+源码讲解 Ribbon 如何获取注册中心的实例
图解+源码讲解 Ribbon 原理初探
图解+源码讲解 Ribbon 服务列表更新
图解+源码讲解 Ribbon 服务选择原理
Ribbon 原理初探
eureka 相关文章
eureka-server 项目结构分析
图解+源码讲解 Eureka Server 启动流程分析
图解+源码讲解 Eureka Client 启动流程分析
图解+源码讲解 Eureka Server 注册表缓存逻辑
图解+源码讲解 Eureka Client 拉取注册表流程
图解+源码讲解 Eureka Client 服务注册流程
图解+源码讲解 Eureka Client 心跳机制流程
图解+源码讲解 Eureka Client 下线流程分析
图解+源码讲解 Eureka Server 服务剔除逻辑
图解+源码讲解 Eureka Server 集群注册表同步机制

从哪里开始分析

    想想什么时候进行服务选择呢,指定是在请求访问的时候,会进行具体的服务选择,那我们就来看看这块执行的代码,看看里面有没有具体的相关代码

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

    一眼就看到了获取服务的方法 getServer(loadBalancer, hint),那我们就从这行代码开始分析,下面先看一个1择服务原理流程图

选择服务原理图

image.png

源码分析

1. 获取服务 DynamicServerListLoadBalancer

    其实他是从负载均衡器中获取的服务列表 getServer(loadBalancer, hint),这个负载均衡器就是 DynamicServerListLoadBalancer,他就是之前在RibbonClientConfiguration 里面定义好的,其实我们看上面的流程图,里面已经获取到了两个服务节点【192.168.0.107:9100、192.168.0.107:9200】了

2. loadBalancer 选择服务

    loadBalancer.chooseServer 这个选择服务的方法其实走的就是 BaseLoadBalancer类里面的 chooseServer 方法

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    if (loadBalancer == null) {
        return null;
    }
    return loadBalancer.chooseServer(hint != null ? hint : "default");
}

    其实是通过一定的服务选择规则进行服务选择的,rule.choose(key) 这块代码是核心的选择规则

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;
        }
    }
}

3. 通过轮询规则选择服务

    默认的服务选择规则是轮询算法,就是一个一个的进行访问的,chooseRoundRobinAfterFiltering 这个方法是最重要的,他是将负载均衡器中的服务列表传入进去之后通过循环算法选择一个出来,我们进入这个方法里面看看就明白了

@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;
    }       
}

4. 轮询算法选择过滤 chooseRoundRobinAfterFiltering

    先通过 getEligibleServers 这个方法选择出合适的服务列表,返回一个List 集合,之后通过 incrementAndGetModulo(eligible.size()) 这个取模算法进新服务选择

    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())));
    }

5. 取模算法 incrementAndGetModulo

    一个for的死循环,通过一个原子类nextIndex 记录当前的值,之后通过当前值+1与服务列表进行取余,得出来的余数设置给 current 并返回它,其实就是计算出一个索引值,这个索引值一定是小于服务列表的size的,因为它需要从 List 这个集合中通过索引 get 出一个值的

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

    通过 eligible.get(current) 获取出来一个服务节点之后进行返回提供给请求进行后续的访问

小结

  1. 从 DynamicServerListLoadBalancer 负载均衡器进行服务列表获取
  2. 通过轮询算法进行服务列表选择
  3. 轮询算法是通过取模算法进行计算的