zoukankan      html  css  js  c++  java
  • Feign使用分析

    feign使用

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

    Feign实现RestTemplate+Ribbon效果

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

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    

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

    @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端口的服务

    @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的情况下,会出现超时的情况,

    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效果

    首先是先开启熔断器

    feign:
      hystrix:
        enabled: true
    

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

    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 15000
    

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

    @Component
    public class FeignClientFallBack implements ResumeFeignClient {
        @Override
        public Integer findDefaultResumeState(Long userId) {
            return -1;
        }
    }
    

    在调用方增加降级配置

    @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请求压缩和响应压缩配置

    配置属性如下

    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中设置具体的类的日志响应级别

    logging:
     level:
    # Feign⽇志只会对⽇志级别为debug的做出响应
     com.lagou.edu.controller.service.ResumeServiceFeignClient:
    debug
    

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

    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将会打印请求的所有信息如下
    file

    Feign源码简要分析

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

    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata,
    			BeanDefinitionRegistry registry) {
    		registerDefaultConfiguration(metadata, registry);
    		registerFeignClients(metadata, registry);
    	}
    

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

    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返回的类型情况

    @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操作中内容

    	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类中的这个方法中

        public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
    

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

      @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方法

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

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

      @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方法
    file
    最终在该方法中实现了远程调用和负载均衡

    欢迎搜索关注本人与朋友共同开发的微信面经小程序【大厂面试助手】和公众号【微瞰技术】,以及总结的分类面试题https://github.com/zhendiao/JavaInterview

    file
    file

  • 相关阅读:
    【翻译】在Sencha应用程序中使用插件和混入
    Codeforces Round #306 (Div. 2) A
    自己定义控件-仿iphone之ToggleButton&amp;VoiceSeekBar
    .m文件导入C++头文件带来的错误
    Permutations
    ceph命令拷屏
    Azure 3 月新公布
    Azure 本月最新活动,速度Mark!
    直接刷脸?一元就能搞定会议签到!
    Azure SQL的DTU和eDTU到底是个什么鬼
  • 原文地址:https://www.cnblogs.com/zhendiao/p/15013404.html
Copyright © 2011-2022 走看看