前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Feign使用分析

Feign使用分析

作者头像
zhaozhen
发布2021-08-06 11:10:53
5260
发布2021-08-06 11:10:53
举报
文章被收录于专栏:微瞰Java后端开发

feign使用

在实现的效果上来说Feign = RestTemplate+Ribbon+Hystrix

Feign实现RestTemplate+Ribbon效果

Feign实现RestTemplate+Ribbon效果,只需要以下几步 在springcloud项目调用方的pom文件中加入openFeign的配置

代码语言:javascript
复制
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

在启动类中加入@EnableFeignClients 同时使用接口声明的方式来实现接口调用

代码语言:javascript
复制
@FeignClient(name = "zhao-service-resume")
public interface ResumeFeignClient {
    @GetMapping("/resume/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId) ;
}

这个接口的声明与被调用方的实现完全一样,我们需要在声明时@FeignClient(name = "zhao-service-resume")声明被调用的服务,即可按照默认的方式进行调用 使用单元测试测试即可测试到负载均衡的效果,访问两次,分别访问到8081和8082端口的服务

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DeliverApplication8091.class)
public class FeignTest {
    @Autowired
    ResumeFeignClient feignClient;
    @Test
    public void feignTest(){
        Integer port = feignClient.findDefaultResumeState(2195321L);
        System.out.println("测试的结果"+port);
     }

}

那么如何在配置类中配置负载均衡呢?格式如下,同时我们还配置了请求的超时时间,在没有配置hystrix的情况下,会出现超时的情况,

代码语言:javascript
复制
zhao-service-resume:
  ribbon: #请求连接超时时间
    ConnectTimeout: 2000
  #请求处理超时时间
    ReadTimeout: 5000
  #对所有操作都进⾏重试
    OkToRetryOnAllOperations: true
####根据如上配置,当访问到故障请求的时候,它会再尝试访问⼀次当前实例(次数由MaxAutoRetries配置),
  ####如果不⾏,就换⼀个实例进⾏访问,如果还不⾏,再换⼀次实例访问(更换次数由MaxAutoRetriesNextServer配置),
####如果依然不⾏,返回失败信息。
    MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第⼀次调⽤
    MaxAutoRetriesNextServer: 0 #切换实例的重试次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载策略调整

超时的报错feign.RetryableException: Read timed out executing GET http://zhao-service-resume/resume/openstate/2195321 即是没有配置重试的这几个参数也是同样的效果

Feign实现Hystrix效果

首先是先开启熔断器

代码语言:javascript
复制
feign:
  hystrix:
    enabled: true

接着增加超时处理逻辑的相关配置

代码语言:javascript
复制
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 15000

但是即使我在被调用方只线程休眠了十秒,程序依然被熔断了。查阅资料表明,Hystrix将采用feign和hystrix超时时间中较小的那个进行超时判定

增加降级兜底方法

代码语言:javascript
复制
@Component
public class FeignClientFallBack implements ResumeFeignClient {
    @Override
    public Integer findDefaultResumeState(Long userId) {
        return -1;
    }
}

在调用方增加降级配置

代码语言:javascript
复制
@FeignClient(name = "zhao-service-resume",fallback = FeignClientFallBack.class)
public interface ResumeFeignClient {
    @GetMapping("/resume/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId) ;
}

同时可以在@FeignClient中增加path属性,将方法上的公共路径提取到类中

Feign使用上的其他特性

Feign请求压缩和响应压缩配置

配置属性如下

代码语言:javascript
复制
feign:
  compression:
    request:
      enabled: true
      min-request-size: 2048
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
    response:
      enabled: true

以上配置包含的两个属性,min-request-size: 2048表示开启压缩的最小值为2048字节,mime-types为支持压缩的数据类型,当前的这几种类型未默认值

Feign请求日志配置

首先在yml中设置具体的类的日志响应级别

代码语言:javascript
复制
logging:
 level:
# Feign⽇志只会对⽇志级别为debug的做出响应
 com.lagou.edu.controller.service.ResumeServiceFeignClient:
debug

然后就是针对feign的log的配置

代码语言:javascript
复制
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLevel(){
        return Logger.Level.FULL;
    }
}

需要注意的是,此处引入的是feign.Logger,此处表示的含义是feign将会打印请求的所有信息如下

Feign源码简要分析

还是依据前文,依照启动类注解和spring.factories中配置的自动配置类来进行分析,首先我们看@EnableFeignClients注解中的FeignClientsRegistrar的具体内容,实现的依然是Spring中的注入beanDefinition的内容

代码语言:javascript
复制
 @Override
 public void registerBeanDefinitions(AnnotationMetadata metadata,
   BeanDefinitionRegistry registry) {
  registerDefaultConfiguration(metadata, registry);
  registerFeignClients(metadata, registry);
 }

registerDefaultConfiguration注入默认配置我们基本可以确定就是加入一些默认配置,而registerFeignClients就是最终实现逻辑的地方。而最终实现逻辑的地方是在该方法下的

代码语言:javascript
复制
private void registerFeignClient(BeanDefinitionRegistry registry,
   AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
  String className = annotationMetadata.getClassName();
  BeanDefinitionBuilder definition = BeanDefinitionBuilder
    .genericBeanDefinition(FeignClientFactoryBean.class);
  validate(attributes);
  definition.addPropertyValue("url", getUrl(attributes));
  definition.addPropertyValue("path", getPath(attributes));
  String name = getName(attributes);
  definition.addPropertyValue("name", name);
  String contextId = getContextId(attributes);
  definition.addPropertyValue("contextId", contextId);
  definition.addPropertyValue("type", className);
  definition.addPropertyValue("decode404", attributes.get("decode404"));
  definition.addPropertyValue("fallback", attributes.get("fallback"));
  definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
  definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

  String alias = contextId + "FeignClient";
  AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

  boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

  beanDefinition.setPrimary(primary);

  String qualifier = getQualifier(attributes);
  if (StringUtils.hasText(qualifier)) {
   alias = qualifier;
  }

  BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
    new String[] { alias });
  BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
 }

而这个类都依赖于 BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);几乎可以确定注入的时候就是FeignClientFactoryBean这个工厂Bean在起作用,那么我们在进入里面看一看,工厂Bean最重要的就是getObject返回的类型情况

代码语言:javascript
复制
@Override
 public Object getObject() throws Exception {
  return getTarget();
 }

 /**
  * @param <T> the target type of the Feign client
  * @return a {@link Feign} client created with the specified data and the context information
  */
 <T> T getTarget() {
  FeignContext context = applicationContext.getBean(FeignContext.class);
  Feign.Builder builder = feign(context);

  if (!StringUtils.hasText(this.url)) {
   if (!this.name.startsWith("http")) {
    url = "http://" + this.name;
   }
   else {
    url = this.name;
   }
   url += cleanPath();
   return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
     this.name, url));
  }
  if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
   this.url = "http://" + this.url;
  }
  String url = this.url + cleanPath();
  Client client = getOptional(context, Client.class);
  if (client != null) {
   if (client instanceof LoadBalancerFeignClient) {
    // not load balancing because we have a url,
    // but ribbon is on the classpath, so unwrap
    client = ((LoadBalancerFeignClient)client).getDelegate();
   }
   builder.client(client);
  }
  Targeter targeter = get(context, Targeter.class);
  return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
    this.type, this.name, url));
 }

在上述代码中,基本上就是构造客户端并调用的过程,那么最关键的就是实现了Ribbon功能的负载均衡的loadBalance操作中内容

代码语言:javascript
复制
 protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
   HardCodedTarget<T> target) {
  Client client = getOptional(context, Client.class);
  if (client != null) {
   builder.client(client);
   Targeter targeter = get(context, Targeter.class);
   return targeter.target(this, builder, context, target);
  }

  throw new IllegalStateException(
    "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
 }

而 targeter.target这段最后都会执行到feign类中的这个方法中

代码语言:javascript
复制
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

关注到newInstance方法发现最终实现时在ReflectiveFeign类中

代码语言:javascript
复制
  @Override
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

上述可见,最终生成的类实际上一个代理类完成了最终的调用,而在代理对象就完成了最后的负载均衡等处理,生成代理对象使用的死FeignInvocationHandler的invoke方法

代码语言:javascript
复制
  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

最后执行了相关的编解码操作

代码语言:javascript
复制
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

而执行并解码的操作executeAndDecode中最重要的就是client.execute方法,点进去之后发现,居然最终调用的就是LoadBalancerFeignClient.execute方法

最终在该方法中实现了远程调用和负载均衡

代码地址https://github.com/zhendiao/deme-code/tree/main/zp

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微瞰技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • feign使用
    • Feign实现RestTemplate+Ribbon效果
      • Feign实现Hystrix效果
      • Feign使用上的其他特性
        • Feign请求压缩和响应压缩配置
          • Feign请求日志配置
          • Feign源码简要分析
          相关产品与服务
          负载均衡
          负载均衡(Cloud Load Balancer,CLB)提供安全快捷的四七层流量分发服务,访问流量经由 CLB 可以自动分配到多台后端服务器上,扩展系统的服务能力并消除单点故障。轻松应对大流量访问场景。 网关负载均衡(Gateway Load Balancer,GWLB)是运行在网络层的负载均衡。通过 GWLB 可以帮助客户部署、扩展和管理第三方虚拟设备,操作简单,安全性强。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档