精华内容
下载资源
问答
  • 2019-07-18 17:00:00

    什么是Feign?

    Feign是一个java的到http客户端绑定的开源项目。 Feign的主要目标是将Java Http 客户端变得简单。Feign的源码地址:https://github.com/OpenFeign/feign

    Feign的工作原理

    feign是一个伪客户端,即它不做任何的请求处理。Feign通过处理注解生成request,从而实现简化HTTP API开发的目的,即开发人员可以使用注解的方式定制request api模板,在发送http request请求之前,feign通过处理注解的方式替换掉request模板中的参数,这种实现方式显得更为直接、可理解。

    总结

    总到来说,Feign的源码实现的过程如下:

    • 首先通过@EnableFeignCleints注解开启FeignCleint
    • 根据Feign的规则实现接口,并加@FeignCleint注解
    • 程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
    • 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
    • RequesTemplate在生成Request
    • Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
    • 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

    转载于:https://my.oschina.net/wangshuhui/blog/3075874

    更多相关内容
  • 【第四章】详解Feign实现原理

    千次阅读 2022-03-24 16:19:25
    这篇文章主要讲述如何通过Feign去消费服务,以及Feign实现原理的解析。 Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可以帮助我们更快捷、优雅地调用HTTP API。 Feign是⼀个HTTP请求的轻量级客户端框架...

    1.1 Feign概述

    这篇文章主要讲述如何通过Feign去消费服务,以及Feign的实现原理的解析。

    FeignNetflix开发的声明式、模板化的HTTP客户端,Feign可以帮助我们更快捷、优雅地调用HTTP API

    Feign是⼀个HTTP请求的轻量级客户端框架。通过 接口 + 注解的方式发起HTTP请求调用,面向接口编程,而不是像Java中通过封装HTTP请求报文的方式直接调用。服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。让我们更加便捷和优雅的去调⽤基于 HTTPAPI,被⼴泛应⽤在 Spring Cloud的解决⽅案中。

    在前面的文章中可以发现当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻。

    那么有没有更好的解决方案呢?答案是确定的有,Netflix已经为我们提供了一个框架:Feign

    Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。

    Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了RibbonHystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。

    总起来说,Feign具有如下特性:

    • 采用的是基于接口可插拔的注解支持,包括Feign注解和JAX-RS注解;
    • 支持可插拔的HTTP编码器和解码器;
    • 支持Hystrix和它的Fallback,具有熔断降级的能力;
    • 支持Ribbon的负载均衡,具有负载均衡的能力;
    • 支持HTTP请求和响应的压缩。

    这看起来有点像我们Spring MVC模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。

    1.2 为什么使用Feign

    Feign 的首要目标就是减少HTTP 调用的复杂性。在微服务调用的场景中,我们调用很多时候都是基于HTTP协议的服务,如果服务调用只使用提供 HTTP调用服务的 HTTP Client框架(e.g. Apache HttpComponnets、HttpURLConnection OkHttp 等),我们需要关注哪些问题呢?

    在这里插入图片描述
    相比这些 HTTP请求框架,Feign封装了HTTP 请求调用的流程,而且会强制使用者去养成面向接口编程的习惯(因为 Feign 本身就是要面向接口)。

    1.3 Feign详解

    1.3.1 代码示例

    首先第一步,在原来的基础上新建一个Feign模块,接着引入相关依赖,引入Feign依赖,会自动引入Hystrix依赖的,如下所示:

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

    application.yml配置如下所示:

    server:
      port: 8083
    spring:
      application:
        name: feign-consumer
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
    

    接着在前面文章中的的的两个provider1provider2两个模块的服务新增几个方法,如下代码所示:

    @RestController
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello(){
            System.out.println("访问来1了......");
            return "hello1";
        }
    
        @RequestMapping("/hjcs")
        public List<String> laowangs(String ids){
            List<String> list = new ArrayList<>();
            list.add("laowang1");
            list.add("laowang2");
            list.add("laowang3");
            return list;
        }
    
        //新增的方法
        @RequestMapping(value = "/hellol", method= RequestMethod.GET)
        public String hello(@RequestParam String name) {
            return "Hello " + name;
        }
    
        @RequestMapping(value = "/hello2", method= RequestMethod.GET)
        public User hello(@RequestHeader String name, @RequestHeader Integer age) {
            return new User(name, age);
        }
    
        @RequestMapping(value = "/hello3", method = RequestMethod.POST)
        public String hello (@RequestBody User user) {
            return "Hello "+ user. getName () + ", " + user. getAge ();
        }
    
    }
    

    接着是上面代码所需用到的User类,代码如下所示:

    public class User {
    
        private String name;
        private Integer age;
    
        //序列化传输的时候必须要有空构造方法,不然会出错
        public User() {
        }
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
          //...getter setter省略
    }
    

    接下来用Feign@FeignClient(“服务名称”)映射服务调用。代码如下所示:

    package hjc;
    
    import org.springframework.cloud.netflix.feign.FeignClient;
    import org.springframework.web.bind.annotation.*;
    
    //configuration = xxx.class  这个类配置Hystrix的一些精确属性
    //value=“你用到的服务名称”
    
    @FeignClient(value = "hello-service",fallback = FeignFallBack.class)
    public interface FeignService {
      //服务中方法的映射路径
        @RequestMapping("/hello")
        String hello();
    
        @RequestMapping(value = "/hellol", method= RequestMethod.GET)
        String hello(@RequestParam("name") String name) ;
    
        @RequestMapping(value = "/hello2", method= RequestMethod.GET)
        User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);
    
        @RequestMapping(value = "/hello3", method= RequestMethod.POST)
        String hello(@RequestBody User user);
    }
    

    接着在Controller层注入FeiService这个接口,进行远程服务调用,代码如下:

    @RestController
    public class ConsumerController {
    
        @Autowired
        FeignService feignService;
    
        @RequestMapping("/consumer")
        public String helloConsumer(){
            return feignService.hello();
        }
    
        @RequestMapping("/consumer2")
        public String helloConsumer2(){
            String r1 = feignService.hello("hjc");
            String r2 = feignService.hello("hjc", 23).toString();
            String r3 = feignService.hello(new User("hjc", 23));
            return r1 + "-----" + r2 + "----" + r3;
        }
    
    }
    

    接着在Feign模块的启动类哪里打上Eureka客户端的注解@EnableDiscoveryClient Feign客户端的注解@EnableFeignClients,代码如下所示:

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class FeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(FeignApplication.class, args);
        }
    }
    

    接着启动启动类,浏览器上输入localhost:8083/consumer 运行结果如下所示:

    在这里插入图片描述

    在这里插入图片描述

    可以看到负载均衡轮询出现hello1,hello2

    接着继续在浏览器上输入localhost:8083/consumer2,运行结果如下:

    在这里插入图片描述

    接下来我们进行Feign声明式调用服务下的,服务降级的使用,那么我们就必须新建一个FeignFallBack类来继承FeiService,代码如下:

    package hjc;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class FeignFallBack implements FeignService{
      //实现的方法是服务调用的降级方法
        @Override
        public String hello() {
            return "error";
        }
    
        @Override
        public String hello(String name) {
            return "error";
        }
    
        @Override
        public User hello(String name, Integer age) {
            return new User();
        }
    
        @Override
        public String hello(User user) {
            return "error";
        }
    }
    

    接着我们再把那两个服务提供模块provider1,provider2模块进行停止,运行结果如下所示:

    在这里插入图片描述

    可以看到我们这几个调用,都进行了服务降级了。

    那么如果我们想精确的控制一下Hystrix的参数也是可以的,比方说跟Hystrix结合的参数,那么可以在FeignClient注解里面配置一个Configuration=XXX类.class属性,在哪个类里面精确的指定一下属性。

    或者在application.yml里面配置,如下:

    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutinMilliseconds: 5000
    
    ribbon:
      connectTimeout: 500
    
    #如果想对单独的某个服务进行详细配置,如下
    hello-service:
      ribbon:
        connectTimeout: 500
    

    1.3.2 参数处理

    Feign处理远程服务调用时,传递参数是通过HTTP协议传递的,参数存在的位置是请求头或请求体中。请求头传递的参数必须依赖@RequestParam注解来处理请求参数,请求体传递的参数必须依赖@RequestBody注解来处理请求参数。

    1.3.2.1 代码环境如下

    Contronller层通过feignClient调用微服务 获取所有任务

    @Controller
    @RequestMapping("tsa/task")
    public class TaskController{
        @Autowired
        TaskFeignClient taskFeignClient;
     
        @PostMapping("/getAll")
        @ResponseBody
        public List<TaskVO> getAll() {
            List<TaskVO> all = taskFeignClient.getAll();
            return all;
        }
    }
    

    @FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入。

    Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。

    @FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
    public interface TaskFeignClient {
        @PostMapping(value = "taskApiController/getAll")
        List<TaskVO> getAll();
    }
    

    微服务端

    @Slf4j
    @RestController
    @RequestMapping("taskApiController")
    public class TaskApiController{
     
        @Autowired
        private TaskService taskService;
     
        @PostMapping("/getAll")
        public List<TaskVO> getAll() {
            log.info("--------getAll-----");
            List<TaskVO>  all = taskService.getAll();
            return all;
        }
    }
    

    1.3.2.2 几个坑

    1、坑一

    首先再次强调Feign是通过http协议调用服务的,重点是要理解这句话

    如果FeignClient中的方法有@PostMapping注解 则微服务TaskApiController中对应方法的注解也应当保持一致为@PostMapping

    如果不一致,则会报404的错误

    调用失败后会触发它的熔断机制,如果@FeignClient中不写@FeignClient(fallback = TaskFeignClientDegraded.class),会直接报错:

    11:00:35.686 [http-apr-8086-exec-8] DEBUG c.b.p.m.b.c.AbstractBaseController - Got an exception
    com.netflix.hystrix.exception.HystrixRuntimeException: TaskFeignClient#getAll() failed and no fallback available.
        at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
        at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
    

    2、坑2:这个是最惨的了

    自己写好的微服务没有运行起来,然后自己的客户端调用这个服务怎么也调用不成功还不知道问题在哪,当时自己微服务运行后,控制台如下:

    Process finished with exit code 0
    

    我以前以为Process finished with exit code 1才是运行失败的意思 ,所以只要出现 Process finished with exit code就说明运行失败

    服务成功启动的标志为:

    11:29:16.483 [restartedMain] INFO  c.b.p.ms.tsa.TsaServiceApplication - Started TsaServiceApplication in 37.132 seconds (JVM running for 39.983)
    

    3、坑3、RequestParam.value() was empty on parameter 0

    如果在FeignClient中的方法有参数传递一般要加@RequestParam(“xxx”)注解

    错误写法

    @FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
    public interface TaskFeignClient {
        @PostMapping(value = "taskApiController/getAll")
        List<TaskVO> getAll(String name);
    }
    

    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll(@RequestParam String name);
    

    正确写法

    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll(@RequestParam("name") String name);
    

    在微服务那边可以不写这个注解,这个也是自己开发的时候烦的小错误,吸取教训。

    疑问
    SpringMVCSpringboot 中都可以使用 @RequestParam 注解,不指定 value的用法,为什么到了 Spring cloud中的Feign 这里就不行了呢?

    这是因为和 Feign的实现有关。Feign 的底层使用的是httpclient,在低版本中会产生这个问题,听说高版本中已经对这个问题修复了。

    4、 坑四 FeignClientpost传递对象和`consumes = "application/json"

    按照坑三的意思,应该这样写

    @FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
    public interface TaskFeignClient {
        @PostMapping(value = "taskApiController/getAll")
        List<TaskVO> getAll(@RequestParam("vo") TaskVO vo);
    }
    

    很意外报错

    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - proxyReceptorRequest = false
    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - proxyTicketRequest = false
    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - requiresAuthentication = false
    16:00:34.415 [hystrix-service-tsa-2] DEBUG c.b.p.m.b.f.PrimusSoaFeignErrorDecoder - 
    error json:{
    "timestamp":1543564834395,
    "status":500,
    "error":"Internal Server Error",
    "exception":"org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException",
    "message":"Failed to convert value of type 'java.lang.String' to required type 'com.model.tsa.vo.TaskVO'; 
    nested exception is java.lang.IllegalStateException: 
    Cannot convert value of type 'java.lang.String' to required type 'com.model.tsa.vo.TaskVO':
    no matching editors or conversion strategy found","path":"/taskApiController/getAll"  }
    

    看着错误信息想了半天突然想明白了:
    Feign本质是通过http 请求的,http怎么能直接传递对象呢,一般都是把对象转换为json通过post请求传递的。

    正确写法应当如下

    @FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
    public interface TaskFeignClient {
        @PostMapping(value = "taskApiController/getAll",,consumes = "application/json")
        List<TaskVO> getAll(TaskVO vo);
    }
    

    也可以这样写

      @PostMapping(value = "taskApiController/getAll")
      List<TaskVO> getAll(@RequestBody TaskVO vo);
    

    此时不用,consumes = "application/json"

    但是第一种写法最正确的 因为FeignClient是在我们本地直接调用的,根本不需要这个注解,Controller调用方法的时候就是直接将对象传给FeignClient,而FeignClient通过http调用服务时则需要将对象转换成json传递。

    微服务代码如下所示:

    @Slf4j
    @RestController
    @RequestMapping("taskApiController")
    public class TaskApiController{
     
        @Autowired
        private TaskService taskService;
     
        @PostMapping("/getAll")
        public List<TaskVO> getAll(@RequestBody TaskVO vo) {
            log.info("--------getAll-----");
            List<TaskVO>  all = taskService.getAll();
            return all;
        }
    }
    

    我第一次写这个的时候方法参数里面什么注解都没加,可以正常跑通,但是传过去的对象却为初始值,实际上那是因为对象根本就没传。

    当然还是推荐使用post请求传递对象的:
    在使用Feign来调用Get请求接口时,如果方法的参数是一个对象,例如:

    @FeignClient ( "microservice-provider-user" )  
    public  interface  UserFeignClient {
          
       @RequestMapping (value =  "/user" , method = RequestMethod.GET)   
       public  User get0(User user);       
    }
    

    那么在调试的时候你会一脸懵逼,因为报了如下错误:

    feign.FeignException: status  405  reading UserFeignClient#get0(User); content:
     
    { "timestamp" : 1482676142940 , "status" : 405 , "error" : "Method Not Allowed" ,  "exception" : "org.springframework.web.HttpRequestMethodNotSupportedException" , "message" : "Request method 'POST' not supported" , "path" : "/user" }
    

    明明定义的Get请求,怎么被转换成了Post?

    调整不用对象传递,一切OK,没毛病,可仔细想想,你想写一堆长长的参数吗?用一个不知道里边有什么鬼的Map吗?或者转换为post?这似乎与REST风格不太搭,会浪费url资源,我们还需要在url定义上来区分Get或者Post

    我很好奇,我定义的Get请求怎么就被转成了Post,于是就开始逐行调试,直到我发现了这个:

    private  synchronized  OutputStream getOutputStream0()  throws  IOException {
            
       try  {   
           if (! this .doOutput) {  
                 throw  new  ProtocolException( "cannot write to a URLConnection if doOutput=false - call setDoOutput(true)" );        
      }  else  {      
           if ( this .method.equals( "GET" )) {    
                this .method =  "POST" ;
      }
    

    这段代码是在 HttpURLConnection 中发现的,jdk原生的http连接请求工具类,这个是Feign默认使用的连接工具实现类,但我记得我们的工程用的是apachhttpclient替换掉了原生的UrlConnection,我们用了如下配置:

    feign:    
       httpclient:
         enabled:  true
    

    同时在依赖中引入apachehttpclient

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version> 4.5.3 </version>
    </dependency>
    

    发现我们少配置了一个依赖:

    <!-- 使用Apache HttpClient替换Feign原生httpclient -->       
    <dependency>     
       <groupId>com.netflix.feign</groupId>     
       <artifactId>feign-httpclient</artifactId>
       <version>${feign-httpclient}</version>    
    </dependency>
    

    那我加上这个依赖后,请求通了,但是接口接收到对象里边属性值是NULL;再看下边的定义是不是少点什么

    @RequestMapping (value =  "/user" , method = RequestMethod.GET)
       public  User get0(User user);
    

    对,少了一个注解:@RequestBody,既然使用对象传递参数,那传入的参数会默认放在RequesBody中,所以在接收的地方需要使用@RequestBody来解析,最终就是如下定义:

    @RequestMapping (value =  "/user" , method = RequestMethod.GET,consumer="application/json")   
       public  User get0( @RequestBody  User user);
    

    1.3.2.3 传递对象的另一种方法和多参传递

    1、GET请求多参数的URL
    假设我们请求的URL包含多个参数,例如http://microservice-provider-user/get?id=1&username=张三 ,要怎么办呢?

    我们知道Spring CloudFeign添加了Spring MVC的注解支持,那么我们不妨按照Spring MVC的写法尝试一下:

    @FeignClient("microservice-provider-user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get0(User user);
    }
    

    然而我们测试时会发现该写法不正确,我们将会收到类似以下的异常:

    feign.FeignException: status 405 reading UserFeignClient#get0(User); content:
    {"timestamp":1482676142940"status":405"error":"Method Not Allowed""exception":"org.springframework.web.HttpRequestMethodNotSupportedException""message":"Request method 'POST' not supported""path":"/get"}
    

    由异常可知,尽管指定了GET方法,Feign依然会发送POST请求。

    正确写法如下:

    (1) 方法一

    @FeignClient(name = "microservice-provider-user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get1(@RequestParam("id") Long id, @RequestParam("username") String username);
    }
    

    这是最为直观的方式,URL有几个参数,Feign接口中的方法就有几个参数。使用@RequestParam注解指定请求的参数是什么。

    (2) 方法二

    @FeignClient(name = "microservice-provider-user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get2(@RequestParam Map<StringObject> map);
    }
    

    多参数的URL也可以使用Map去构建

    当目标URL参数非常多的时候,可使用这种方式简化Feign接口的编写。

    POST请求包含多个参数
    下面我们来讨论如何使用Feign构造包含多个参数的POST请求。

    实际就是坑四,把参数封装成对象传递过去就可以了

    1.3.2.4 最后总结一下

    Feign的Encoder、Decoder和ErrorDecoder

    Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。

    默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。

    注意,如果在@RequetMapping中的method将请求方式指定为get,那么所有未标注解的参数将会被忽略,例如:

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);
    

    此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。

    Spring Cloud环境下,FeignEncoder只会用来编码没有添加注解的参数。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。

    FeignHTTP Client

    Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTPpersistence connection。我们可以用ApacheHTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring CloudBrixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Clientfeign-httpclient依赖:

    <!-- 使用Apache HttpClient替换Feign原生httpclient -->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
            </dependency>
            <dependency>
                <groupId>com.netflix.feign</groupId>
                <artifactId>feign-httpclient</artifactId>
                <version>${feign-httpclient}</version>
            </dependency>
    

    然后在application.properties中添加:

    feign.httpclient.enabled=true
    

    通过Feign, 我们能把HTTP远程调用对开发者完全透明,得到与调用本地方法一致的编码体验。这一点与阿里Dubbo中暴露远程服务的方式类似,区别在于Dubbo是基于私有二进制协议,而Feign本质上还是个HTTP客户端。如果是在用Spring Cloud Netflix搭建微服务,那么Feign无疑是最佳选择。

    1.4 调用原理解析

    Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示。

    在这里插入图片描述
    从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request请求。通过Feign以及JAVA的动态代理机制,使得Java开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

    Feign优化
    (1)GZIP压缩
    gzip是一种数据格式,采用deflate算法压缩数据。当Gzip压缩到一个纯文本数据时,可以减少70%以上的数据大小。

    gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。

    只配置Feign请求-应答的GZIP压缩

    # feign gzip
    # 局部配置。只配置feign技术相关的http请求-应答中的gzip压缩。
    # 配置的是application client和application service之间通讯是否使用gzip做数据压缩。
    # 和浏览器到application client之间的通讯无关。
    # 开启feign请求时的压缩, application client -> application service
    feign.compression.request.enabled=true
    # 开启feign技术响应时的压缩,  application service -> application client
    feign.compression.response.enabled=true
    # 设置可以压缩的请求/响应的类型。
    feign.compression.request.mime-types=text/xml,application/xml,application/json
    # 当请求的数据容量达到多少的时候,使用压缩。默认是2048字节。
    feign.compression.request.min-request-size=512
    配置全局的GZIP压缩
    
    # spring boot gzip
    # 开启spring boot中的gzip压缩。就是针对和当前应用所有相关的http请求-应答的gzip压缩。
    server.compression.enabled=true
    # 哪些客户端发出的请求不压缩,默认是不限制
    server.compression.excluded-user-agents=gozilla,traviata
    # 配置想压缩的请求/应答数据类型,默认是 text/html,text/xml,text/plain
    server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain
    # 执行压缩的阈值,默认为2048
    server.compression.min-response-size=512
    
    展开全文
  • springCloud Feign实现原理

    千次阅读 2022-03-05 13:02:09
    Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix 实现负载均衡和断路器,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。 Spring Cloud ...

    一、Feign介绍

    Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。
    Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix 实现负载均衡和断路器,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。

    Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。

    深入理解ribbon

    二、Feign解决了什么问题?

    封装了Http调用流程,更适合面向接口化的变成习惯

    三、Feign远程调用的基本流程

    Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程

    在这里插入图片描述
    Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

    四、Feign的HTTP客户端

    Feign的HTTP客户端支持3种框架,分别是;HttpURLConnection、HttpClient、OKHttp。Feign中默认使用HttpURLConnection。

    1、HttpURLConnection是JDK自带的HTTP客户端技术,并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的其他方案,也没有必要自己去管理连接对象。
    2、Apache提供的HttpClient框架相比传统JDK自带的HttpURLConnection,它封装了访问http的请求头,参数,内容体,响应等等;它不仅使客户端发送HTTP请求变得容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性;另外高并发大量的请求网络的时候,还是用“HTTP连接池”提升吞吐量。
    3、OKHttp是一个处理网络请求的开源项目,是安卓端最火热的轻量级框架。OKHttp拥有共享Socket,减少对服务器的请求次数,通过连接池,减少了请求延迟等技术特点。

    配置httpclient客户端

    feign:
      httpclient:
      enabled: true
    

    五、负载均衡

    在使用@FeignClient注解的时候 是默认使用了ribbon进行客户端的负载均衡的,默认的是轮询策略,那么如果我们想要更改策略的话,需要修改消费者yml中的配置,如下:

    order-service:  #服务名
      ribbon:
     
    #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
    #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
    #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
    #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
        ConnectTimeout: 500 #请求连接超时时间
        ReadTimeout: 1000 #请求处理的超时时间
        OkToRetryOnAllOperations: true #对所有请求都进行重试
        MaxAutoRetriesNextServer: 2 #切换实例的重试次数
        MaxAutoRetries: 1 #对当前实例的重试次数
    
    

    这里我们可以看到ribbon的策略主要有以下几种:

    com.netflix.loadbalancer.RandomRule #配置规则 随机
    com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
    com.netflix.loadbalancer.RetryRule #配置规则 重试
    com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
    随机:几个提供者间随机访问

    轮询:轮流访问

    重试:在一段时间内通过RoundRobinRule选择服务实例,一段时间内没有选择出服务则线程终止

    响应时间权重:根据平均响应时间来计算权重,权重意思是,请求时间越久的server,其被分配给客户端使用的可能性就越低

    展开全文
  • 一、Feign原理 二、Feign使用


    SpringCloud 总架构图

    请添加图片描述

    Spring Cloud 常见的集成方式是使用Feign+Ribbon技术来完成服务间远程调用及负载均衡的,如下图

    在这里插入图片描述

    1. 在微服务启动时,会向服务发现中心上报自身实例信息,这里ServiceB 包含多个实例。 每个实例包括:IP地址、端口号信息。

    2. 微服务会定期从Nacos Server(服务发现中心)获取服务实例列表。

    3. 当ServiceA调用ServiceB时,ribbon组件从本地服务实例列表中查找ServiceB的实例,如获取了多个实例如:Instance1、Instance2。这时ribbon会通过用户所配置的负载均衡策略从中选择一个实例。

    4. 最终,Feign组件会通过ribbon选取的实例发送http请求。

    采用Feign+Ribbon的整合方式,是由Feign完成远程调用的整个流程。而Feign集成了Ribbon,Feign使用Ribbon

    完成调用实例的负载均衡。

    一、简介

    1.1、负载均衡的概念

    在SpringCloud服务协议流程中,ServiceA通过负载均衡调用ServiceB,下边来了解一下负载均衡

    负载均衡就是将用户请求(流量)通过一定的策略,分摊在多个服务实例上执行,它是系统处理高并发、缓解网络

    压力和进行服务端扩容的重要手段之一。它分为服务端负载均衡客户端负载均衡

    服务器端负载均衡:

    在这里插入图片描述

    客户端负载均衡:

    在这里插入图片描述

    2.2、Feign概念

    Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。

    Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

    **封装了Http调用流程,更符合面向接口化的编程习惯。**类似Dubbo服务调用。

    项目主页:https://github.com/OpenFeign/feign

    二、入门案例

    使用Feign替代RestTemplate发送Rest请求。使之更符合面向接口化的编程习惯。

    实现步骤:

    1. 导入feign依赖starter
    2. 编写Feign客户端接口
    3. 消费者启动引导类开启Feign功能注解
    4. 访问接口测试

    实现过程:

    2.1、导入依赖

    • 在consumer-service中添加spring-cloud-starter-openfeign依赖

    • <!--配置feign-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      

    2.2、Feign的客户端

    • 在consumer-service中编写Feign客户端接口类ConsumerService

    • @FeignClient(value = "provider-service")
      public interface ConsumerService {
          
          //String url = String.format("http://provider-service/user/findUserById/%s",id);
          @RequestMapping("/user/findUserById/{id}")
          User findUserById(@PathVariable("id") Integer id);
      
      }
      
      • Feign会通过动态代理,帮我们生成实现类。
      • 注解@FeignClient声明Feign的客户端,指明服务名称
      • 接口定义的方法,采用SpringMVC的注解。Feign会根据注解帮我们生成URL地址

    2.3、调用Feign

    • 编写ConsumerFeignController,使用ConsumerService访问

      • @Autowired注入ConsumerService

      • @RestController
        @RequestMapping("/consumer")
        public class ConsumerController {
        
            @Autowired
            private ConsumerService consumerService;
        
            @GetMapping("/findUserById/{id}")
            public User findUserById(@PathVariable Integer id){
                return consumerService.findUserById(id);
            }
        
        }
        

    2.4、开启Feign功能

    • 在ConsumerApplication启动类上,添加@EnableFeignClients注解,开启Feign功能

      @SpringBootApplication
      @EnableFeignClients  //开启feign
      @EnableDiscoveryClient
      public class ConsumerApplication {
          public static void main(String[] args) {
              SpringApplication.run(ConsumerApplication.class,args);
          }
      }
      

    2.5、启动测试

    访问接口http://localhost:8081/consumer/findUserById/1,正常获取结果:

    在这里插入图片描述

    2.6、Feign实现原理简单分析

    在这里插入图片描述

    Feign帮我们做了哪些事儿:

    • 在 声明Feign客户端 之后,Feign会根据@FeignClient注解使用java的动态代理技术生成代理类,在这里我们指定@FeignClient value为serviceB,则说明这个类的远程目标为spring cloud的服务名称为serviceB的微服务。

    • serviceB的具体访问地址,Feign会交由ribbon获取,若该服务有多个实例地址,ribbon会采用指定的负载均衡策略选取实例。

    • Feign兼容spring的web注解(如:@GetMapping),它会分析声明Feign客户端方法中的Spring注解,得出Http请求method、参数信息以及返回信息结构。

    • 当业务调用Feign客户端方法时,会调用代理类,根据以上分析结果,由代理类完成实际的参数封装、远程http请求,返回结果封装等操作。

    三、负载均衡(Ribbon)

    Feign本身集成了Ribbon,因此不需要额外引入依赖。在这里插入图片描述

    Ribbon是一个客户端负载均衡器,它的责任是从一组实例列表中挑选合适的实例,如何挑选?取决于负载均衡策

    Ribbon核心组件IRule是负载均衡策略接口,它有如下实现,大家仅做了解:

    • RoundRobinRule(默认):轮询,即按一定的顺序轮换获取实例的地址。

    • RandomRule:随机,即以随机的方式获取实例的地址。

    • AvailabilityFilteringRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;

    • WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大,被选中的机率越高; 刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够时,会切换到WeightedResponseTimeRule

    • RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用的服务;

    • BestAvailableRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;

    • ZoneAvoidanceRule: 默认规则,复合判断server所在区域的性能和server的可用性选择服务器;

    可通过下面方式在spring boot 配置文件中修改默认的负载均衡策略:

    account‐service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule 
    

    account-service 是调用的服务的名称,后面的组成部分是固定的。

    四、熔断器支持

    Feign本身也集成Hystrix熔断器,starter内查看。在这里插入图片描述

    服务降级方法实现步骤:

    1. 在配置文件application.yml中开启feign熔断器支持
    2. 编写FallBack处理类,实现FeignClient客户端
    3. 在@FeignClient注解中,指定FallBack处理类。
    4. 测试服务降级效果

    实现过程:

    1. 在配置文件application.yml中开启feign熔断器支持:默认关闭

      feign:
      	hystrix:
      		enabled: true # 开启Feign的熔断功能
      
    2. 定义一个类ConsumerServiceImpl,实现刚才编写的ConsumerService,作为FallBack的处理类

      @Component
      public class ConsumerServiceImpl implements ConsumerService {
      
          //熔断方法
          @Override
          public User findUserById(Integer id) {
              User user = new User();
              user.setId(id);
              user.setNote("网络异常,请稍后再试...");
              return user;
          }
      }
      
    3. 在@FeignClient注解中,指定FallBack处理类。。

      @FeignClient(value = "provider-service",fallback = ConsumerServiceImpl.class)
      public interface ConsumerService {
      
          //String url = String.format("http://provider-service/user/findUserById/%s",id);
          @RequestMapping("/user/findUserById/{id}")
          User findUserById(@PathVariable("id") Integer id);
      
      }
      
    4. 重启测试:关闭provider-service服务,然后在页面访问;http://localhost:8081/consumer/findUserById/1

    五、请求压缩和响应压缩

    SpringCloudFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。

    通过配置开启请求与响应的压缩功能:

    feign:
    	compression:
            request:
                enabled: true # 开启请求压缩
            response:
                enabled: true # 开启响应压缩
    
    

    也可以对请求的数据类型,以及触发压缩的大小下限进行设置

    #  Feign配置
    feign:
    	compression:
    		request:
    			enabled: true # 开启请求压缩
    			mime-types:	text/html,application/xml,application/json # 设置压缩的数据类型
    			min-request-size: 2048 # 设置触发压缩的大小下限
    			#以上数据类型,压缩大小下限均为默认值
    
    

    六、配置日志级别

    在发送和接收请求的时候,Feign定义了日志的输出定义了四个等级:这里我们配置测试一下。

    级别说明
    NONE不做任何记录
    BASIC只记录输出Http 方法名称、请求URL、返回状态码和执行时间
    HEADERS记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息
    FULL记录Request 和Response的Header,Body和一些请求元数据

    实现步骤:

    1. 在application.yml配置文件中开启日志级别配置
    2. 编写配置类,定义日志级别bean。
    3. 在接口的@FeignClient中指定配置类
    4. 重启项目,测试访问

    实现过程:

    1. 在consumer-service的配置文件中设置com.itheima包下的日志级别都为debug

      • # com.itheima 包下的日志级别都为Debug
        logging:
          level:
            com.itheima: debug
        
    2. 在consumer-service编写配置类,定义日志级别

      • @Configuration
        public class FeignLogLevleConfig {
        
            //采用full打印日志
            @Bean
            public Logger.Level configLog(){
                return Logger.Level.FULL;
            }
        }
        
    3. 在consumer-service的ConsumerService中指定配置类

      • @FeignClient(value = "provider-service",
                fallback = ConsumerServiceImpl.class,
                configuration = FeignLogLevleConfig.class)
        public interface ConsumerService {
        
        
            @RequestMapping("/user/findUserById/{id}")
            User findUserById(@PathVariable("id") Integer id);
        
        }
        
    4. 重启项目,即可看到每次访问的日志

      • 在这里插入图片描述
    展开全文
  • Feign的工作原理

    千次阅读 多人点赞 2020-04-28 18:20:57
    文章目录Feign的简单介绍Feign的工作原理1.创建远程接口的本地代理实例2.封装Request对象并进行编码3.`feign.Client`发送请求并对获取结果进行解码总结 Feign的简单介绍 Feign组件主要用于微服务项目中,用来简化...
  • Spring Cloud Feign相关原理分析

    千次阅读 2019-05-24 12:31:49
    1.认识Spring Cloud Feign 在使用Spring Cloud构建的微服务架构中,Feign用来提供声明式的服务客户端定义方式。 Feign是一个伪客户端(不做任何的请求处理),它通过处理注解生成request,以此来简化HTTP API的开发。 ...
  • 本片文章重在理解spring的扩展机制。...现象:在使用feign,或者mybatis等框架的时候,都有一个特点就是会先定义一个接口,比如:mybaits的mapper, feign的 client。但是我们知道,spring的在帮我们
  • Feign原理 (图解)

    万次阅读 多人点赞 2020-07-28 21:17:51
    1.1简介:Feign远程调用的 Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用...
  • 经过微服务精通之Ribbon原理解析的学习,我们了解到了服务消费者获取服务提供者实例的过程,都是通过RestTemplate来实现的,而且,都是模板化操作。那spring cloud是否有哪个组件可以通过注解或者配置的方式,来简化...
  • springclound 之feign原理源码解析及使用详解 目录 feign作用 feign是一款基于注解和动态代理实现的声明式restful http客户端,它提供了类似retrofit、 jaxrs的api和使用方式,但他更加简洁、扩展性也很强,能...
  • feign 调用原理、实例

    2022-04-15 18:08:02
    2. 原理 2.1 user 调用消费者的接口; 2.2 消费者调用 服务者 client 依赖中的 feign 接口; 2.3 消费者中配置了 feign 接口的服务器地址和端口号; 2.4 服务者服务处理接口消费者发出的请求,处理并返回结果; ...
  • Hystrix、Feign技术底层实现原理

    千次阅读 2020-05-26 11:55:30
    一.Feign的设计原理 1.1Feign是什么 Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。...
  • Spring Cloud-Feign设计原理

    万次阅读 多人点赞 2018-09-26 15:42:50
    什么是FeignFeign 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数...
  • OpenFeign 实现原理

    2022-01-08 22:47:48
    虽然 OpenFeign 只能支持基于文本的网络请求,但是它可以极大简化网络请求的实现,方便编程人员快速构建自己的网络请求应用。 核心组件与概念 在阅读源码时,可以沿着两条线路进行,一是被@FeignClient注解修饰的...
  • 了解Feign的作用 掌握Feign的使用过程 掌握Feign的负载均衡配置 掌握Feign的熔断配置 掌握Feign的压缩配置 掌握Feign的日志配置 一、 Feign简介 Feign [feɪn] 译文 伪装。Feign是一个声明式WebService客户端....
  • Feign工作原理如下: 1、 启动类添加@EnableFeignClients注解,Spring会扫描标记了@FeignClient注解的接口,并生成此接口的代理对象 2、 @FeignClient(value = "XC_SERVICE_MANAGE_CMS")即指定了cms的服务名称,
  • Feign实现服务之间调用的内部原理

    千次阅读 2020-10-14 21:31:30
    目录Feign 的调用过程服务调用传递复杂数据处理 Feign 的调用过程 /** * Feign 的调用过程 SynchronousMethodHandler对象 * 1、构造请求数据,将数据转化成为json * RequestTemplate template = ...
  • 主要介绍了SpringCloud之Feign远程接口映射的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 实战系列-被面试官问到Feign原理

    千次阅读 2020-06-29 17:00:54
    Feign实现中是由下面这类来进行实现的FeignInvocationHandler。而这个类是数据才feign-core模块中里面feign.ReflectiveFeign类的一个静态内部类。 static class FeignInvocationHandler implements ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,895
精华内容 4,758
关键字:

feign的实现原理