图解+源码讲解 Feign 如何选取指定服务

1,336 阅读3分钟

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

图解+源码讲解 Feign 如何选取指定服务

骐骥一跃,不能十步;驽马十驾,功在不舍 -- 荀子

feign 相关文章
图解+源码讲解 Feign 如何将客户端注入到容器中
图解+源码讲解动态代理获取 FeignClient 代理对象
图解+源码讲解代理对象 ReflectiveFeign 分析
图解+源码讲解 Feign 如何选取指定服务
图解+源码讲解 Feign 请求的流程
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 集群注册表同步机制

服务选择代码

private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
        @Override
        public void call(Subscriber<? super Server> next) {
            try {
                // 获取服务,根据负载均衡上下文获取服务,其实就是ribbon 的负载均衡器
                Server server = loadBalancerContext
                    .getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {
                next.onError(e);
            }
        }
    });
}

LoadBalancerContext#getServerFromLoadBalancer

public Server getServerFromLoadBalancer(@Nullable URI original,
      @Nullable Object loadBalancerKey) throws ClientException {
    // 获取负载均衡器 DynamicServerListLoadBalancer ,里面包含着两个服务
    // Servers=[192.168.60.1:9100, 192.168.60.1:9200]
    // 更新注册列表的 serverListUpdater
    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        // Partial URI or no URI Case
        // well we have to just get the right instances from lb - or we fall back
        if (lb != null){
            // 进行负载均衡器中的服务列表选择
            Server svc = lb.chooseServer(loadBalancerKey);

服务选择

    走的是 ZoneAwareLoadBalancer 的 chooseServer 方法,因为ZoneAwareLoadBalancer 实现了 DynamicServerListLoadBalancer,其实后面走的和ribbon 选择服务的列表一样

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

BaseLoadBalancer#chooseServer

    调用默认的选择算法进行服务选择

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

轮询算法 PredicateBasedRule#choose

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

根据轮询算法选择服务并且过滤#chooseRoundRobinAfterFiltering

    通过 getEligibleServers 算法进行服务过滤,之后通过 incrementAndGetModulo 这个方法进行索引计算,之后通过 eligible.get(索引值)获取出服务实例

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

    到这里发现其实和我前面说的一样就是ribbon的服务选择算法

if (svc == null){
       //  192.168.60.1:9100
        host = svc.getHost();
        return svc;
} 

    到此我们就选择出来了我们想要的地址 192.168.60.1:9100

结果展示

image.png

注册中心显示

image.png
    看出来我们通过负载均衡选择的服务就是从注册中心获取的服务实例

小结

    其实说白了 feign 封装了 ribbon,利用了它的负载均衡策略,进行服务选择操作,比如用了它的负载均衡器 DynamicServerListLoadBalancer,当服务选择出来之后进行后续的地址访问,指定还有后续的地址解析方法【reconstructURIWithServer(server, request.getUri())】,最后通过 HttpUrlConnetcion 工具进行访问,进行最后的结果返回