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

原创 吴就业 144 0 2020-06-28

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/0192e62d3f3c4456b4f2386af99d4753

作者:吴就业
链接:https://wujiuye.com/article/0192e62d3f3c4456b4f2386af99d4753
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2020年06月28日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。

Spring Cloud Kubernetes微服务实战与源码分析

本篇内容:

OpenFeign与Feign的关系

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搜出的一道面试题,你们觉得这个答案正确吗?

RibbonFegin整合的桥梁是FeignLoadBalancer

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

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);
            }
        };
    }
#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

Spring Cloud动态配置实现原理与源码分析

本篇从源码分析Spring Cloud实现动态配置的原理。Spring Cloud实现动态配置需要结合Spring源码分析。

Spring Cloud Kubernetes服务注册与发现实现原理与源码分析

本篇分析Spring Cloud Kubernetes服务注册与发现实现原理,以及Spring Cloud Kubernetes Core&Discovery源码分析。

Ribbon重试策略RetryHandler的配置与源码分析

本篇我们再对Ribbon的重试机制地实现做详细分析,从源码分析找出我们想要地答案,即如何配置Ribbon实现调用每个服务的接口使用不一样的重试策略,如配置失败重试多少次,以及自定义重试策略RetryHandler。

Spring Cloud Ribbon源码分析

本篇继续分析OpenFeign是如何与Ribbon整合、Ribbon是如何实现负载均衡的、Ribbon是如何从注册中心获取服务的。

Spring Cloud OpenFeign源码分析,为什么不导入Ribbon应用会启动不起来?

如果指定了URL,那么getOptional方法不会返回null,且返回的Client是LoadBalancerFeignClient,但不会抛出异常。如果不指定URL,则走负载均衡逻辑,走的是loadBalance方法,且抛出异常。

将分布式项目sck-demo部署到本地kubernetes

本篇介绍如何搭建本地Kubernetes集群,以及将分布式项目sck-demo部署到本地kubernetes,以及实现版本升级和回滚。