Feign高级实战-源码分析

news2024/11/19 6:29:56

目录

  • 参考
  • 导读
  • 什么是Feign
  • Feign 和 Openfeign 的区别
  • OpenFeign的启动原理
    • 在启动类申明@EnableFeignClients
    • registerDefaultConfiguration
    • registerFeignClients
    • registerFeignClient
    • getTarget()创建一个代理对象
    • HttpClientFeignLoadBalancerConfiguration
  • OpenFeign 的工作原理
    • 动态代理生成
  • Feign 如何负载均衡
  • Nacos FeignClient调用原理
    • NacosDiscoveryClient.java
  • 总结如下:

参考

花一个周末,掌握 OpenFeign 核心原理
Nacos Feign调用研究

导读

  • 什么是 Feign

  • Feign 和 Openfeign 的区别

  • OpenFeign 的启动原理

  • OpenFeign 的工作原理

什么是Feign

Feign 是声明式 Web 服务客户端,它使编写 Web 服务客户端更加容易

Feign 不做任何请求处理,通过处理注解相关信息生成 Request,并对调用返回的数据进行解码,从而实现 简化 HTTP API 的开发在这里插入图片描述
如果要使用 Feign,需要创建一个接口并对其添加 Feign 相关注解,另外 Feign 还支持可插拔编码器和解码器,致力于打造一个轻量级 HTTP 客户端

Feign 和 Openfeign 的区别

Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由社区进行维护,更名为 Openfeign

为了少打俩字,下文简称 Opefeign 为 Feign

并将原项目迁移至新的仓库,所以我们在 Github 上看到 Feign 的坐标如下
在这里插入图片描述

Starter Openfeign

当然了,基于 SpringCloud 团队对 Netflix 的情有独钟,你出了这么好用的轻量级 HTTP 客户端,我这老大哥不得支持一下,所以就有了基于 Feign 封装的 Starter
在这里插入图片描述

Spring Cloud 添加了对 Spring MVC 注解的支持,并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters

另外,Spring Cloud 老大哥同时集成了 Ribbon 和 Eureka 以及 Spring Cloud LoadBalancer,以在使用 Feign 时提供负载均衡的 HTTP 客户端

针对于注册中心的支持,包含但不限于 Eureka,比如 Consul、Naocs 等注册中心均支持

在我们 SpringCloud 项目开发过程中,使用的大多都是这个 Starter Feign

OpenFeign的启动原理

在启动类申明@EnableFeignClients

要启用FeignClient首先必须在启动类上加上注解@EnableFeignClients,EnableFeignClients代码如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
  ....

注意到注解@Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,在启动时会执行registerBeanDefinitions动态注册

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar {
  @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
  ...
}

registerDefaultConfiguration

我们首先看registerDefaultConfiguration,代码不多,直接贴代码

private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }
  • 获取配置信息defaultAttrs

  • 注册默认配置类信息,配置类从defaultConfiguration中获取并且名称为"default." + metadata.getClassName()比如启动类为TestApplication,那么名称为default.com.test.TestApplication

  • 1.获取 @EnableFeignClients 注解上的属性以及对应 Value

  • 2.生成 FeignClientSpecification(存储 Feign 中的配置类) 对应的构造器 BeanDefinitionBuilder

  • 3.FeignClientSpecification Bean 名称为 default. + @EnableFeignClients 修饰类全限定名称 + FeignClientSpecification

  • 4.@EnableFeignClients defaultConfiguration 默认为 {},如果没有相关配置,默认使用 FeignClientsConfiguration 并结合 name 填充到 FeignClientSpecification,最终注册为 IOC

registerClientConfiguration将配置信息注册为FeignClientSpecification

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

FeignClientSpecification其实就是一个key-value结构体,key就是配置名称,value就是配置类

FeignClientSpecification(String name, Class<?>[] configuration) {
        this.name = name;
        this.configuration = configuration;
    }

EnableFeignClients注解中参数defaultConfiguration为全局配置类,如果FeignClient没有配置则会获取defaultConfiguration的配置,defaultConfiguration可以配置为任意类,比如

@EnableFeignClients(defaultConfiguration = GlobalFeignClientConfiguration.class)

GlobalFeignClientConfiguration该如何指定配置呢?我们知道可以通过feign.Builder来手动创建FeignClient,在feign.Builder中有以下变量

public static class Builder {
    private Logger.Level logLevel = Logger.Level.NONE;
    private Contract contract = new Contract.Default();
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
  ...

}

这些都可以通过defaultConfiguration重新定义,比如下面这个指定了Loger.Level

public class GlobalFeignClientConfiguration {
    @Bean
    public Level level() {
        return Level.FULL;
    }
}

当然也可以通过配置文件进行配置

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

registerFeignClients

registerFeignClients先扫描所有带注解@FeignClient的类,并注册到Spring容器中
FeignClientsRegistrar#registerFeignClient方法

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {

        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        //...扫描FeignClient类(部分代码省略)

        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
        
        //... 注册Feign客户端(部分代码省略)
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));

                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
  • 扫描 @EnableFeignClients 注解,如果有 clients,则加载指定接口,为空则根据 scanner 规则扫描出修饰了 @FeignClient 的接口

  • 获取 @FeignClient 上对应的属性,根据 configuration 属性去创建接口级的 FeignClientSpecification 配置类 IOC Bean

  • 将 @FeignClient 的属性设置到 FeignClientFactoryBean 对象上,并注册 IOC Bean

@FengnClient 修饰的接口实际上使用了 Spring 的代理工厂生成代理类,所以这里会把修饰了 @FeignClient 接口的 BeanDefinition 设置为 FeignClientFactoryBean 类型,而 FeignClientFactoryBean 继承自 FactoryBean
,也就是说,当我们定义 @FeignClient 修饰接口时,会将Client注册为FeignClientFactoryBean,这样Spring Boot在获取Feign实例时就会调用FeignClientFactoryBean.getTarget方法

registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       String className = annotationMetadata.getClassName();
        Class clazz = ClassUtils.resolveClassName(className, null);
        ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
                ? (ConfigurableBeanFactory) registry : null;
        String contextId = getContextId(beanFactory, attributes);
        String name = getName(attributes);
    //实例化FeignClientFactoryBean工厂
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
    //实例化BeanDefinition构建器,在注入容器时会回调lambda表达式
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { 
      //设置@FeignClient注解设置的url
            factoryBean.setUrl(getUrl(beanFactory, attributes));
      //设置@FeignClient注解上path属性
            factoryBean.setPath(getPath(beanFactory, attributes));
            factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
            Object fallback = attributes.get("fallback");
            if (fallback != null) { 
                factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
                        : ClassUtils.resolveClassName(fallback.toString(), null));
            }
            Object fallbackFactory = attributes.get("fallbackFactory");
            if (fallbackFactory != null) { 
                factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
                        : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
            }
      //调用工厂的getObject方法
            return factoryBean.getObject();
        });
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        definition.setLazyInit(true);
        validate(attributes);
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
        beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
        // has a default, won't be null
        boolean primary = (Boolean) attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String[] qualifiers = getQualifiers(attributes);
        if (ObjectUtils.isEmpty(qualifiers)) { 
            qualifiers = new String[] {  contextId + "FeignClient" };
        }
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    //将FeignClientFactoryBean实例注入容器之中
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

上述代码在将FeignClientFactoryBean实例注入容器时后会调用FeignClientFactoryBean类的getObject方法;

rg.springframework.cloud.openfeign.FeignClientFactoryBean#getObject方法

@Override
    public Object getObject() { 
        return getTarget();
    }

getTarget()创建一个代理对象

最终会通过targeter.target创建一个代理对象
org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget方法是确定请求调用哪个client的核心

<T> T getTarget() {
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
                : applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        //微服务@FeignClient的url为null,则会执行如下代码
        if (!StringUtils.hasText(url)) { 
            if (!name.startsWith("http")) { 
                url = "http://" + name;
            }
            else { 
                url = name;
            }
            url += cleanPath();
      // 此处调用feign.Feign.Builder#target(feign.Target<T>)
      //构建ReflectiveFeign对象
      //构建AOP切面请求代理
            return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
        }
    //非微服务@FeignClient的url为非空,则执行如下代码
        if (StringUtils.hasText(url) && !url.startsWith("http")) { 
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
    //获取上下文中的Client对象
        Client client = getOptional(context, Client.class);
        if (client != null) { 
            if (client instanceof FeignBlockingLoadBalancerClient) { 
                // 非load balancing,因为存在url
                // 获取FeignBlockingLoadBalancerClient的代理,即AppacheHttpClient
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            if (client instanceof RetryableFeignBlockingLoadBalancerClient) { 
                // 非load balancing,因为存在url
                // 获取FeignBlockingLoadBalancerClient的代理,即AppacheHttpClient
                client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
            }
      //初始化构造器为AppacheHttpClient
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
      // 此处调用feign.Feign.Builder#target(feign.Target<T>)
      //构建ReflectiveFeign对象
      //构建AOP切面请求代理
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,而是会返回执行了工厂 Bean 中 FactoryBean#getObject 逻辑的实例

创建代理对象的代码位于feign.Feign.target中,至此整个Feign从初始化到最终实例化就全部完成

org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance方法获取loadBalance客户端

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { 
    //获取微服务请求的FeignBlockingLoadBalancerClient或RetryableFeignBlockingLoadBalancerClient
        Client client = getOptional(context, Client.class);
        if (client != null) { 
      //初始化构造器的Client对象
            builder.client(client);
            Targeter targeter = get(context, Targeter.class);
      // 此处调用feign.Feign.Builder#target(feign.Target<T>)
      //构建ReflectiveFeign对象
      //构建AOP切面请求代理
            return targeter.target(this, builder, context, target);
        }
        throw new IllegalStateException(
                "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
    }

经上述代码分析可知@FeignClient注解的url为空时,client为loadbalancer微服务对象,其Client对象是FeignBlockingLoadBalancerClient、RetryableFeignBlockingLoadBalancerClient;而url为非空时为普通请求,则Client对象是ApacheHttpClient实例对象,其获取ApacheHttpClient对象是通过getDelegate方法获取,这又是为何呢?

HttpClientFeignLoadBalancerConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnBean({  LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
@Import(HttpClientFeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
class HttpClientFeignLoadBalancerConfiguration { 
    @Bean
    @ConditionalOnMissingBean
    @Conditional(OnRetryNotEnabledCondition.class)
    public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
            LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) { 
        ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
    //未开启重试实例对象,代理是ApacheHttpClient
        return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
    }
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnBean(LoadBalancedRetryFactory.class)
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
            matchIfMissing = true)
    public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
            LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
            LoadBalancerClientFactory loadBalancerClientFactory) { 
        ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
    //开启重试实例化对象,代理为ApacheHttpClient
        return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
                properties, loadBalancerClientFactory);
    }
}

OpenFeign 的工作原理

说 Feign 的工作原理,核心点围绕在被 @FeignClient 修饰的接口,如何发送及接收 HTTP 网络请求

上面说到 @FeignClient 修饰的接口最终填充到 IOC 容器的类型是 FeignClientFactoryBean,先来看下它是什么在这里插入图片描述

FactoryBean 接口特征

这里说一下 FeignClientFactoryBean 都有哪些特征

  • 它会在类初始化时执行一段逻辑,依据 Spring InitializingBean 接口

  • 如果它被别的类 @Autowired 进行注入,返回的不是它本身,而是 FactoryBean#getObject 返回的类,依据 Spring FactoryBean 接口

  • 它能够获取 Spring 上下文对象,依据 Spring ApplicationContextAware 接口

先来看它的初始化逻辑都执行了什么
在这里插入图片描述

没有特别的操作,只是使用断言工具类判断两个字段不为空。ApplicationContextAware 也没什么说的,获取上下文对象赋值到对象的局部变量里,重点以及关键就是 FactoryBean#getObject 方法

getTarget 源码方法还是挺长的,这里采用分段的形式展示
在这里插入图片描述

这里提出一个疑问?FeignContext 什么时候、在哪里被注入到 Spring 容器里的?

在这里插入图片描述

看到图片小伙伴就明了了,用了 SpringBoot 怎么会不使用自动装配的功能呢,FeignContext 就是在 FeignAutoConfiguration 中被成功创建

在这里插入图片描述

初始化父子容器

feign 方法里日志工厂、编码、解码等类均是通过 get(…) 方法得到
在这里插入图片描述
这里涉及到 Spring 父子容器的概念,默认子容器 Map 为空,获取不到服务名对应 Context 则新建
在这里插入图片描述
从下图中看到,注册了一个 FeignClientsConfiguration 类型的 Bean,我们上述方法 feign 中的获取的编码、解码器等组件都是从此类中获取默认
在这里插入图片描述
默认注册如下,FeignClientsConfiguration 是由创建 FeignContext 调用父类 Super 构造方法传入的 在这里插入图片描述
关于父子类容器对应关系,以及提供 @FeignClient 服务对应子容器的关系(每一个服务对应一个子容器实例)
在这里插入图片描述
回到 getInstance 方法,子容器此时已加载对应 Bean,直接通过 getBean 获取 FeignLoggerFactory
在这里插入图片描述
如法炮制,Feign.Builder、Encoder、Decoder、Contract 都可以通过子容器获取对应 Bean
在这里插入图片描述
configureFeign 方法主要进行一些配置赋值,比如超时、重试、404 配置等,就不再细说赋值代码了

到这里有必要总结一下创建 Spring 代理工厂的前半场代码

  • 注入@FeignClient 服务时,其实注入的是 FactoryBean#getObject 返回代理工厂对象

  • 通过 IOC 容器获取 FeignContext 上下文

  • 创建 Feign.Builder 对象时会创建 Feign 服务对应的子容器

  • 从子容器中获取日志工厂、编码器、解码器等 Bean

  • 为 Feign.Builder 设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置

动态代理生成

在这里插入图片描述
因为我们在 @FeignClient 注解是使用 name 而不是 url,所以会执行负载均衡策略的分支
在这里插入图片描述
Client: Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端

代码中的 Client、Targeter 在自动装配时注册,配合上文中的父子容器理论,这两个 Bean 在父容器中存在

因为我们并没有对 Hystix 进行设置,所以走入此分支
在这里插入图片描述
创建反射类 ReflectiveFeign,然后执行创建实例类
在这里插入图片描述
newInstance 方法对 @FeignClient 修饰的接口中 SpringMvc 等配置进行解析转换,对接口类中的方法进行归类,生成动态代理类
在这里插入图片描述
可以看出 Feign 创建动态代理类的方式和 Mybatis Mapper 处理方式是一致的,因为两者都没有实现类

根据 newInstance 方法按照行为大致划分,共做了四件事

  • 处理 @FeignCLient 注解(SpringMvc 注解等)封装为 MethodHandler 包装类

  • 遍历接口中所有方法,过滤 Object 方法,并将默认方法以及 FeignClient 方法分类

  • 创建动态代理对应的 InvocationHandler 并创建 Proxy 实例

  • 接口内 default 方法 绑定动态代理类

MethodHandler 将方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储
在这里插入图片描述
到这里我们也就可以 Feign 的工作方式了。前面那么多封装铺垫,封装个性化配置等等,最终确定收尾的是创建动态代理类

也就是说在我们调用 @FeignClient 接口时,会被 FeignInvocationHandler#invoke 拦截,并在动态代理方法中执行下述逻辑

  • 接口注解信息封装为 HTTP Request

  • 通过 Ribbon 获取服务列表,并对服务列表进行负载均衡调用(服务名转换为 ip+port)

  • 请求调用后,将返回的数据封装为 HTTP Response,继而转换为接口中的返回类型

既然已经明白了调用流程,那就正儿八经的试一哈,试过才知有没有…
在这里插入图片描述
RequestTemplate:构建 Request 模版类

Options:存放连接、超时时间等配置类

Retryer:失败重试策略类

重试这一块逻辑看了很多遍,但是怎么看,一个 continue 关键字放到 while 的最后面都有点多余 作者:花前月下奈何秦深 https://www.bilibili.com/read/cv11237986 出处:bilibili

执行远端调用逻辑中使用到了 Rxjava (响应式编程),可以看到通过底层获取 server 后将服务名称转变为 ip+port 的方式

这种响应式编程的方式在 SpringCloud 中很常见,Hystix 源码底层也有使用
在这里插入图片描述
网络调用默认使用 HttpURLConnection,可以配置使用 HttpClient 或者 OkHttp 调用远端服务后,再将返回值解析正常返回,到这里一个完成的 Feign 调用链就聊明白了

Feign 如何负载均衡

一般而言,我们生产者注册多个服务,消费者调用时需要使用负载均衡从中 选取一个健康并且可用的生产者服务

在这里插入图片描述

因为 Feign 内部集成 Ribbon,所以也支持此特性,一起看下它是怎么做的
在这里插入图片描述

我们在 Nacos 上注册了两个服务,端口号 8080、8081。在获取负载均衡器时就可以获取服务集合
在这里插入图片描述
然后通过 chooseServer 方法选择一个健康实例返回,后面会新出一篇文章对 Ribbon 的负载均衡详细说明
在这里插入图片描述
通过返回的 Server 替换 URL 中的服务名,最后使用网络调用服务进行远端调用,完美的一匹

Nacos FeignClient调用原理

如果使用Nacos作为注册中心,只要在依赖中添加nacos的starter并且在配置文件中指定nacos的地址就接入完成

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

接入nacos后,Feign调用将从nacos配置中心获取获取服务信息,比较重要的就是服务的地址和端口,那么这一切时如何实现的,我们就从spring-cloud-starter-alibaba-nacos-discovery开始,spring-cloud-starter-alibaba-nacos-discovery本身没有包含任何的代码(不知道为何这样设计),但依赖了spring-cloud-alibaba-nacos-discovery

我们这里关注NacosDiscoveryClientConfigServiceBootstrapConfiguration

@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@ImportAutoConfiguration({ NacosDiscoveryClientAutoConfiguration.class,
        NacosDiscoveryAutoConfiguration.class })
public class NacosDiscoveryClientConfigServiceBootstrapConfiguration {

}

这里使用了注解ImportAutoConfiguration,该注解会自动导入配置的类。 我们进入NacosDiscoveryClientAutoConfiguration

@Configuration
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class })
public class NacosDiscoveryClientAutoConfiguration {
    @Bean
    public DiscoveryClient nacosDiscoveryClient(
            NacosDiscoveryProperties discoveryProperties) {
        return new NacosDiscoveryClient(discoveryProperties);
    }
  //...省略部分代码
}

这里会注入一个NacosDiscoveryClient对象,该对象实现接口DiscoveryClient。DiscoveryClient在spring cloud中负责从注册中心获取服务列表,也就是说底层Feign并不是直接和注册中心打交道,而是通过DiscoveryClient发现服务,那么只要实现了DiscoveryClient就能实现自定义注册中心

NacosDiscoveryClient.java

public class NacosDiscoveryClient implements DiscoveryClient {

    private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class);
    public static final String DESCRIPTION = "Spring Cloud Nacos Discovery Client";

    private NacosDiscoveryProperties discoveryProperties;

    public NacosDiscoveryClient(NacosDiscoveryProperties discoveryProperties) {
        this.discoveryProperties = discoveryProperties;
    }

    @Override
    public String description() {
        return DESCRIPTION;
    }

    @Override
    public List<ServiceInstance> getInstances(String serviceId) {
        try {
            String group = discoveryProperties.getGroup();
            List<Instance> instances = discoveryProperties.namingServiceInstance()
                    .selectInstances(serviceId, group, true);
            return hostToServiceInstanceList(instances, serviceId);
        }
        catch (Exception e) {
            throw new RuntimeException(
                    "Can not get hosts from nacos server. serviceId: " + serviceId, e);
        }
    }
  //...省略部分代码

}

以获取服务实例getInstances为例,discoveryProperties.namingServiceInstance()获取了一个NamingService对象,这个对象包含nacos api所有的操作,有了这个对象就能和nacos通信。

总结如下:

通过 @EnableFeignCleints 注解启动 Feign Starter 组件

  • Feign Starter 在项目启动过程中注册全局配置,扫描包下所有的 @FeignClient 接口类,并进行注册 IOC 容器

  • @FeignClient 接口类被注入时,通过 FactoryBean#getObject 返回动态代理类

  • 接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request

  • 交由 Ribbon 进行负载均衡,挑选出一个健康的 Server 实例

  • 继而通过 Client 携带 Request 调用远端服务返回请求响应

  • 通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多策略协同改进的阿基米德优化算法及其应用(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

论文阅读:On the User Behavior Leakage from Recommender System Exposure

论文地址 Motivation&#xff1a; 现阶段对于用户行为的保护仅仅从用户端来考虑&#xff0c;比如用户的行为数据等。然而推荐系统是一个闭环的过程&#xff0c;即用户交互了物品&#xff0c;推荐系统根据用户的交互信息去推荐物品&#xff0c;用户也会根据推荐系统推荐的物品做…

[Java] 浅析rpc的原理及所用到的基本底层技术

文章目录前言阅读前须知rpc是什么&#xff1f;别的进程 vs 别的机器rpc的目的或是我们为什么需要rpc&#xff1f;实现rpc所涉及到的底层技术1. 通信技术&#xff08;网络IO、Network IO&#xff09;套接字&#xff08;Socket&#xff09;bio、nio与Netty2. 网络协议&#xff08…

【仿真建模】第三课:AnyLogic入门基础课程 - 多层建筑行人疏散仿真讲解

文章目录一、Agent类的概念二、行人疏散仿真2.1 仿真模型示意图2.2 具体实现步骤一、Agent类的概念 二、行人疏散仿真 2.1 仿真模型示意图 2.2 具体实现步骤 首先&#xff0c;新建模型 新建一个MyFloor1对象&#xff0c;代表第一个楼层 创建矩形墙&#xff0c;并放到原点…

专业数采软件DXP OPC Server售后问题解决方案

DeviceXPlorer OPC Server是一套实现工业自动化设备数据读取或发送的软件。它提供与制造车间中的控制设备&#xff08;如 PLC、机床和机器人&#xff09;的连接&#xff0c;支持200多种设备通讯协议&#xff0c;便捷的配置&#xff0c;快速实现设备联网采集。 在与设备通讯方面…

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

idea永久设置maven配置,新项目不用再设置

在这里设置就是永久的设置&#xff0c;新项目将使用该设置&#xff0c;maven的配置也在新项目和新模块创建的时候直接加载 英文的话&#xff0c;看位置大概也应该可以找到 点开后左上角搜索maven&#xff0c;找到如图maven的设置 主路径就是maven的安装包软件的路径 用户设置…

LeetCode 110平衡二叉树 257.二叉树的所有路径 404左叶子之和

文章目录110平衡二叉树c 代码实现python 代码实现257.二叉树的所有路径c代码实现python 代码实现404左叶子之和c 代码实现python 代码110平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个…

http 知识整理

1. 启发式缓存 在不设置cache-control/expires的情况下&#xff0c;浏览器不会默认进入协商缓存。而是根据Date/LastModified去自动计算出合适的缓存时间。 计算方式为&#xff1a;(Date - LastModified) * n n&#xff1a;LM-Factor&#xff0c;处于[0,1]之间 2. 强制缓存 -…

Vue的模版代码与数据绑定方式

目录 模版代码 插值语法 指令语法 数据多层访问 vue模版语小结 数据绑定方式 模版代码 插值语法 插值语法就是使用{{xxx}}描述的 <div id"root">{{name}} </div> 指令语法 <div id"root"><a :href"school.url">…

lazada买家订单导出

下载安装与运行 https://www.yuque.com/webcrawl/handbook/mtad3q 用途与功能 所见即所得的导出自由选择导出项支持Excel、JSON两种方式导出自由排序Excel导出列顺序导出过程中有进度提示&#xff0c;用户可以随时提前中止 导出过程演示 选择lazada订单导出&#xff0c;开始…

linux内核整体架构

操作系统概念 操作系统属于软件范畴&#xff0c;负责管理系统的硬件资源。OS具备的功能&#xff1a;1.为应用程序提供执行环境。2.为多用户和应用程序管理计算机的硬件资源。3.虚拟化功能。4.支持并发。 宏内核与微内核架构 宏内核&#xff1a;所有的内核代码都编译成二进制…

基于JAVA的学生课程后台管理系统【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86427641 开学选好课是具备学术能力的首要表现。学生不能为了拿高分&#xff0c;只选简单课程&#xff0c;也没有必要为了显示出自己热衷自我挑战&#xff0c;奋不顾身地一头扎进高难度课程。在…

强化深度学习中利用时序差分法中的Sarsa算法解决风险投资问题实战(附源码 超详细必看)

需要源码请点赞关注收藏后评论区留下QQ~~~ 一、Sarsa算法简介 Sarsa算法每次更新都需要获取五元组&#xff08;S,A,R,S,A&#xff09;这也是该算法称为Sarsa的原因&#xff0c;每当从非终止状态进行一次转移后&#xff0c;就进行一次更新&#xff0c;但需要注意的是&#xff0…

【论文阅读】社交网络传播最大化问题-04

Efficient Influence Maximization in Social Networks相关工作改进的贪心算法对独立级联模型的改进对加权级联模型的改进改进度折扣算法影响力最大化&#xff1a;在社交网络中找到一小部分能够最大化传播影响力的节点(种子节点)。一是改进原有的贪心算法&#xff0c;进一步缩短…

KMP算法——通俗易懂讲好KMP算法:实例图解分析+详细代码注解

文章目录1.kmp算法基本介绍2.字符串的最长公共前后缀&部分匹配表2.1 什么是最长公共前后缀2.2 什么是部分匹配表Next2.3 字符串最长公共前后缀&部分匹配表的代码实现2.4 代码测试3.根据部分匹配表搜索字符串匹配位置3.1 匹配成功一个就退出匹配的代码3.1.1 KMP算法的大…

Vue父组件给子组件传参数

别人在调用我们写的组件时&#xff0c;虽然要实现的结构一样&#xff0c;但如果别人想改一下显示的内容或者之类的&#xff0c;该怎么做呢&#xff1b;这时候就要提到“传参数”这个词了&#xff0c;别人可以通过传不同的参数&#xff0c;来实现他们具体的结构&#xff1b; 传参…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.22 RabbitMQ 安装

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.22 RabbitMQ 安装5.22.1 Erlang下载5.22.2 安装5.…

HTML+CSS期末大作业 中国传统美食网站设计 节日美食13页 html5网页设计作业代码 html制作网页案例代码 html大作业网页代码

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

3招学会TikTok电商选品,速看

Sensor Tower商店情报数据显示&#xff0c;2022年10月Instagram以将近6700万下载量&#xff0c;成为全球移动应用&#xff08;非游戏&#xff09;下载榜冠军&#xff0c;较2021年10月增长17.2%。其中&#xff0c;印度市场的下载量占45.2%&#xff0c;美国市场的下载量占比为5.4…