OpenFeign与Ribbon源码分析总结与面试题

feignspring cloud 组件中的一个轻量级 restfulhttp 服务客户端,简化接口调用,将 http 调用转为 rpc 调用,让调用远程接口像调用同进程应用内的接口调用一样简单。

dubborpc 远程调用一样,通过动态代理实现接口的调用。 feign 通过封装包装请求体、发送 http 请求、获取接口响应结果、序列化响应结果等接口调用动作来简化接口的调用。

openfeign 则是 spring cloudfeign 的基础上支持了 spring mvc 的注解,如 @RequesMapping@GetMapping@PostMapping 等。 openfeign 还实现与 Ribbon 的整合。

服务提供者只需要提供 API 接口,而不需要像 dubbo 那样需要强制使用 implements 实现接口,使用 fegin 不要求服务提供者在 Controller 使用 implements 关键字实现接口。

Feign底层实现原理

openfeign 通过包扫描将所有被 @FeignClient 注解注释的接口扫描出来,并为每个接口注册一个 FeignClientFactoryBean<T> 实例FeignClientFactoryBean<T> 是一个 FactoryBean<T> ,当 Spring 调用 FeignClientFactoryBean<T>getObject 方法时, openfeign 返回一个 Feign 生成的动态代理类,拦截方法的执行。

feign 会为代理的接口的每个方法 Method 都生成一个 MethodHandler

当为接口上的 @FeignClient 注解的 url 属性配置服务提供者的 url 时,其实就是不与 Ribbon 整合,由 SynchronousMethodHandler 实现接口方法远程同步调用,使用默认的 Client 实现类 Default 实例发起 http 请求。

当接口上的 @FeignClient 注解的 url 属性不配置时,且会走负载均衡逻辑,也就是需要与 Ribbon 整合使用。这时候不再是使用默认的 ClientDefault )调用接口,而是使用 LoadBalancerFeignClient 调用接口( LoadBalancerFeignClient 也是 Client 接口的实现类,最终还是使用 Default 发起请求),由 LoadBalancerFeignClient 实现与 Ribbon 的整合。

Ribbon是什么

RibbonNetflix 发布的开源项目,提供在服务消费端实现负载均衡调用服务提供者,从注册中心读取所有可用的服务提供者,在客户端每次调用接口时采用如轮询负载均衡算法选出一个服务提供者调用,因此, Ribbon 是一个客户端负载均衡器。

Ribbon 提供多种负载均衡算法的实现、提供重试支持。 Feign 也提供重试支持,在 SynchronousMethodHandlerinvoke 方法中实现,但 Feign 的重试比较简单,只是向同一个服务节点发送请求,而 Ribbon 的失败重试是重新选择一个服务节点调用的,在服务提供者部署多个节点的情况下,显然 Feign重试机制是没有多大意义的。

Ribbon底层实现原理

下图是我在 google 搜出的一道面试题,你们觉得这个答案正确吗?

OpenFeign与Ribbon源码分析总结与面试题

RibbonFegin 整合的桥梁是 FeignLoadBalancer

  • 1、 Ribbon 会注册一个 ILoadBalancer (默认使用实现类 ZoneAwareLoadBalancer )负载均衡器, Feign 通过 LoadBalancerFeignClient 调用 FeignLoadBalancerexecuteWithLoadBalancer 方法来使用 RibbonILoadBalancer 负载均衡器选择一个提供者节点发送 http 请求,实际发送请求还是 OpenFeignFeignLoadBalancer 发起的, Ribbon 从始至终都只负载负载均衡选出一个服务节点。

  • 2、 spring-cloud-netflix-ribbon 的自动配置类会注册一个 RibbonLoadBalancerClient ,此 RibbonLoadBalancerClient 正是 Ribbonspring cloud 的负载均衡接口提供的实现类,用于实现 @LoadBalancer 注解语意。

Ribbon 并非直接通过 DiscoveryClient 从注册中心获取服务的可用提供者,而是通过 ServerList<Server> 从注册中心获取服务提供者, ServerListDiscoveryClient 不一样, ServerList 不是 Spring Cloud 定义的接口,而是 Ribbon 定义的接口。以 spring-cloud-kubernetes-ribbon 为例, spring-cloud-kubernetes-ribbonRibbon 提供 ServerList 的实现 KubernetesServerListRibbon 负责定时调用 ServerListgetUpdatedListOfServers 方法更新可用服务提供者。

怎么去获取可用的服务提供者节点由你自己去实现 ServerList 接口,并将实现的 ServerList 注册到 Spring 容器。如果不提供 ServerList ,那么使用的将是 Ribbon 提供的默认实现类 ConfigurationBasedServerListConfigurationBasedServerList 并不会从注册中心读取获取服务节点,而是从配置文件中读取。

如果我们使用的注册中心是 Eureka ,当我们在项目中添加 spring-cloud-starter-netflix-eureka-client 时,其实就已经往项目中导入了一个 ribbon-eurekajar ,由该 jar 包提供 RibbonEureka 整合所需的 ServerListDiscoveryEnabledNIWSServerList

OpenFeign与Ribbon源码分析总结与面试题

ribbon-eureka 源码地址: https://github.com/Netflix/ribbon/tree/master/ribbon-eureka ,感兴趣的朋友可以看下。

那么,现在你还会说 Ribbon 是通过 DiscoveryClient 从注册中心获取服务提供者的吗?当然,你也完全可以通过自己实现一个 ServerList ,然后通过 DiscoveryClient 从注册中心获取。

Ribbon是如何实现失败重试的?

Ribbon 提供 RetryHandler 接口,并且默认使用 DefaultLoadBalancerRetryHandlerLoadBalancerCommandsubmit 方法中(在 FeignLoadBalancerexecuteWithLoadBalancer 方法中调用),如果配置重试次数大于 0 ,则会调用 RxJavaAPI 支持重试。

public Observable<T> submit(final ServerOperation<T> operation) {
        // .......
        final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
        final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
        // Use the load balancer
        Observable<T> o = (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    @Override
                    public Observable<T> call(Server server) {
                        //.......
                        // 调用相同节点的重试次数
                        if (maxRetrysSame > 0) 
                            o = o.retry(retryPolicy(maxRetrysSame, true));
                        return o;
                    }
                });
        // 调用不同节点的重试次数
        if (maxRetrysNext > 0 && server == null) 
            o = o.retry(retryPolicy(maxRetrysNext, false));
        return o.onErrorResumeNext(...);
    }
复制代码

默认 maxRetrysSame (调用相同的重试次数)为 0 ,默认 maxRetrysNext (调用不同节点的重试次数)为 1retryPolicy 方法是返回的是一个判断是否重试的决策者,由该决策者决定是否需要重试(抛出的异常是否允许重试,是否达到最大重试次数)。

private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
        return new Func2<Integer, Throwable, Boolean>() {
            @Override
            public Boolean call(Integer tryCount, Throwable e) {
                if (e instanceof AbortExecutionException) {
                    return false;
                }
                // 大于最大重试次数
                if (tryCount > maxRetrys) {
                    return false;
                }
                if (e.getCause() != null && e instanceof RuntimeException) {
                    e = e.getCause();
                }
                // 调用RetryHandler判断是否重试
                return retryHandler.isRetriableException(e, same);
            }
        };
    }
复制代码

原文 

https://juejin.im/post/5ef75215e51d4534906638ef

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » OpenFeign与Ribbon源码分析总结与面试题

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址