OpenFeign

OpenFeign

简介

Feign是一个声明式的Web Service客户端,是一种声明式、模板化的HTTP客户端。OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
Feign将Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

官方网址:https://docs.spring.io/spring-cloud-openfeign/docs/2.2.10.BUILD-SNAPSHOT/reference/html/

特点
  1. 可插拔的注解支持,包括Feign注解和JSX-RS注解
  2. 支持可插拔的HTTP编码器和解码器
  3. 支持Hystrix和它的Fallback
  4. 支持Ribbon的负载均衡
  5. 支持HTTP请求和响应的压缩
Feign和openFeign有什么区别
Feign openFeign
Feign是SpringCloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务 OpenFeign 是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

服务环境

使用nacos进行服务注册&服务发现

服务名称 版本
spring-cloud-dependencies 2021.0.5
spring-cloud-alibaba-dependencies 2021.0.4.0
spring-boot-starter-parent 2.7.8

使用过程

![UML 图](/Users/yiwenjie/Downloads/UML 图.jpg)

创建微服务项目,分别提供server服务&client服务

创建Client服务
  • 添加依赖

    <!--bootstrap-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    
    <!--openfeign-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    <!--服务发现-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    <!--服务注册-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
    <!--loadbalancer-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    
  • 添加bootstrap配置文件

    spring:
      application:
        name: client
      profiles:
        active: ${PROFILE_ACTIVE:dev}
      cloud:
        nacos:
          server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
          discovery:
            server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
            group: openfeign
            namespace: ${NACOS_NAMESPACE:openfeign}
            cluster-name: WH
            ephemeral: true
          config:
            server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
            file-extension: yaml
            group: openfeign
            namespace: ${NACOS_NAMESPACE:openfeign}
    
  • Spring boot添加注解@EnableFeignClients,@EnableDiscoveryClient

    @EnableFeignClients,开启openFeign功能

    @EnableDiscoveryClient,开启服务发现功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class OpenFeignConsumerApplication
    {
        public static void main(String[] args) {
            SpringApplication.run(OpenFeignConsumer9006Application.class, args);
        }
    }
    
  • 创建openFeign接口

    @FeignClient(name = "server")
    public interface ServerFeignService {
        @GetMapping(value = "/server")
        String server();
    }
    

    @FeignClient中的value属性指定了服务提供者在nacos注册中心的服务名

创建Server服务
  • 添加依赖

    <!--bootstrap-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    
    <!--服务注册-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
  • 添加bootstrap配置文件

    server:
      port: 8881
    
  • 创建服务端Controller

    @GetMapping("/server")
    public String server() {
        return "server";
    }
    
nacos服务列表

image-20230524152252607

基本工作原理

  • 添加@EnableFeignClients注解开启对@FeignClient注解的扫描加载处理。根据Feign Client的开发规范,定义接口并添加@FeiginClient注解
  • 当程序启动之后,会进行包扫描,扫描所有@FeignClient注解的接口,并将这些信息注入到IOC容器中。当定义的Feign接口被调用时,通过JDK的代理的方式生成具体的RequestTemplate。Feign会为每个接口方法创建一个RequestTemplate对象。该对象封装了HTTP请求需要的所有信息,例如请求参数名、请求方法等信息。
  • 然后由RequestTemplate生成Request,把Request交给Client去处理,这里的Client可以是JDK原生的URLConnection、HttpClient或Okhttp。最后Client被封装到LoadBalanceClient类,看这个类的名字既可以知道是结合Ribbon负载均衡发起服务之间的调用,因为在OpenFeign中默认是已经整合了Ribbon了

参数传递

openFeign默认的传参方式就是JSON传参(@RequestBody),因此定义接口的时候可以不用@RequestBody注解标注,不过为了规范,一般都填上

Get参数传递
  • PathVariable 传递参数

    //server
    @GetMapping("/server/PathVariable/{id}")
    public List<ServerVO> getPathVariable(@PathVariable("id") String id) {
      System.out.println("id:" + id);
    
      return getResult();
    }
    
    //client
    @GetMapping("/server/path/{id}")
    List<ServerVO> getPath(@PathVariable("id") String id);
    
  • RequestParam 传递参数

//server
@GetMapping("/server/RequestParam")
public List<ServerVO> getRequestParam(String id,String name) {
  System.out.println("id:" + id + "name:" + name);

  return getResult();
}

//client
@GetMapping("/server/RequestParam")
List<ServerVO> getRequestParam(@RequestParam("id") String id, @RequestParam("name") String name);
  • POJO 传递参数

    //server
    @GetMapping("/server/Pojo")
    public List<ServerVO> getPojo(ServerDTO serverDTO) {
      System.out.println("id:" + serverDTO.getId() + "name:" + serverDTO.getName());
    
      return getResult();
    }
    
    //client
    @GetMapping("/server/Pojo")
    List<ServerVO> getPojo(@SpringQueryMap ServerDTO serverDTO);
    
Post参数传递
  • RequestBody传递参数

    //server
    @PostMapping("/server/Post")
    public List<ServerVO> getPost(@RequestBody ServerDTO serverDTO) {
      System.out.println("id:" + serverDTO.getId() + "name:" + serverDTO.getName());
    
      return getResult();
    }
    
    //client
    @PostMapping("/server/Post")
    List<ServerVO> getPost(ServerDTO serverDTO);
    

替换默认的httpClient

The OkHttpClient and ApacheHttpClient and ApacheHC5 feign clients can be used by setting feign.okhttp.enabled or feign.httpclient.enabled or feign.httpclient.hc5.enabled to true, respectively, and having them on the classpath. You can customize the HTTP client used by providing a bean of either org.apache.http.impl.client.CloseableHttpClient when using Apache or okhttp3.OkHttpClient when using OK HTTP or org.apache.hc.client5.http.impl.classic.CloseableHttpClient when using Apache HC5.

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection

在生产环境中,通常不使用默认的http client,通常有如下两种选择:

  • 使用ApacheHttpClient
  • 使用OkHttp

替换成ApacheHttpClient

  • 添加依赖

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
    </dependency>
    
  • 配置文件中开启

    feign:
      httpclient:
        enabled: true
    
  • 验证是否替换成功

    image-20230524143902080

超时处理

我们可以在默认客户端命名客户端上配置超时

  • connectTimeout 防止由于服务器处理时间长而阻塞调用者
  • readTimeout 从连接建立时开始应用,在返回响应时间过长时触发

如果服务器未运行或不可用,则数据包会导致连接被拒绝。通信以错误消息或回退结束。如果它设置得非常低,这可能会在 connectTimeout之前发生,执行查找和接收此类数据包所花费的时间会导致此延迟的很大一部分,它可能会根据涉及 DNS 查找的远程主机进行更改。

设置Ribbon的超时时间(不推荐)
ribbon:
  # 建立链接所用的时间,适用于网络状况正常的情况下,两端链接所用的时间
  ReadTimeout: 5000
  # 建立链接后从服务器读取可用资源所用的时间
  ConectTimeout: 5000
设置openFeign的超时时间(推荐)
feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000

default设置的是全局超时时间,对所有的openFeign接口服务都生效

但是正常的业务逻辑中可能涉及到多个openFeign接口的调用,如下图:

img

那么上面配置的全局超时时间能不能通过呢?很显然是serviceAserviceB能够成功调用,但是serviceC并不能成功执行,肯定报超时

此时我们可以给serviceC这个接口对应的服务单独配置一个超时时间,配置如下:

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000
      ## 为serviceC这个服务单独配置超时时间
      serviceC:
        connectTimeout: 30000
        readTimeout: 30000

注意:单个配置的超时时间将会覆盖全局配置。

开启日志增强

为每个创建的 Feign 客户端创建一个记录器,默认情况下,记录器的名称是用于创建 Feign 客户端的接口的完整类名, Feign logging 只响应 DEBUG 级别。

您可以为每个客户端配置的 Logger.Level 对象告诉 Feign 要记录多少。选择是:

  • NONE, 无日志记录(默认)。
  • BASIC, 仅记录请求方法和 URL 以及响应状态代码和执行时间。
  • HEADERS, 记录基本信息以及请求和响应标头。
  • FULL, 记录请求和响应的标头、正文和元数据
1.yaml 文件设置接口日志级别
logging:
  level:
    com.smile.learn.openfeign.client.remote: debug

这里com.smile.learn.openfeign.client.remote是openFeign接口所在的包名,当然你可以设置某一个特定的openFeign接口

2.自定义配置类,设置日志级别
package com.smile.learn.openfeign.client.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author yiwenjie
 */
@Configuration
public class OpenFeignConfig {
    /**
     * 设置日志级别
     * @description 设置日志级别
     * @author smile.Yi
     * @date 2023/5/24 10:11 上午
     * @return feign.Logger.Level
    **/
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

这里的logger是feign包里的logger feign.Logger

显示效果

image-20230524101723702

日志中详细的打印出了请求头、请求体的内容

请求/响应压缩

GZIP

gzip是一种数据格式,采用用deflate算法压缩数据;gzip是一种流行的数据压缩算法,应用十分广泛,尤其是在Linux平台。

网络数据经过压缩后实际上降低了网络传输的字节数,明显的好处就是可以加快网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是GZIP与搜索引擎的抓取工具有着更好的关系。例如 Google就可以通过直接读取GZIP文件来比普通手工抓取更快地检索网页

GZIP压缩传输的原理如下图:

img

按照上图拆解出的步骤如下:

  • 客户端向服务器请求头中带有:Accept-Encoding:gzip,deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。
  • 服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式压缩过的。
  • 客户端接收到响应之后,先判断是否有Content-Encoding消息头,如果有,按该格式解压报文。否则按正常报文处理。

openFeign支持请求/响应开启GZIP压缩,整体的流程如下图:

img

上图中涉及到GZIP传输的只有两块,分别是Application client -> Application ServiceApplication Service->Application client

注意:openFeign支持的GZIP仅仅是在openFeign接口的请求和响应,即是openFeign消费者调用服务提供者的接口

开启GZIP

openFeign开启GZIP步骤也是很简单,只需要在配置文件中开启如下配置

feign:
  ## 开启压缩
  compression:
    request:
      enabled: true
      ## 开启压缩的阈值,单位字节,默认2048,即是2k,这里为了演示效果设置成10字节
      min-request-size: 10
      mime-types: text/xml,application/xml,application/json
    response:
      enabled: true

默认的client无法开启gzip,需要替换成httpclient后,才能开启(待定)

验证是否成功

image-20230524144143629

注解解析

FeignClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {...}

从FeignClient的注解可以看得出,ElementType.TYPE说明FeignClient的作用目标是接口。其常用的属性如下:

  • name:执行FeignClient的名称,如果项目中使用Ribbon,name属性会作为微服务的名称,用作服务发现。
  • url:url一般用于调试,可以手动指定@FeignClient调用的地址
  • decode404:当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException。
  • configuration:Feigin配置类,可自定义Feign的Encode,Decode,LogLevel,Contract。
  • fallback:定义容错的类,当远程调用的接口失败或者超时的时候,会调用对应接口的容错罗杰,fallback执行的类必须实现@FeignClient标记的接口。在OpenFeign的依赖中可以发现,集成Hystrix。
  • fallbackFactory:工厂类,用于生成fallback类实例,通过此属性可以实现每个接口通用的容错逻辑,以达到减少重复的代码。
  • path:定义当前FeignClient的统一前缀。

异常处理

feign.FeignException$BadGateway: [502 Bad Gateway] during [GET] to [http://server/server] [ServerFeignService#server()]: []

这是由于服务器异常导致

java.net.UnknownHostException: server: nodename nor servname provided, or not known

添加依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Caused by: java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?

添加依赖

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

https://blog.csdn.net/weixin_45764765/article/details/128825554

Exception:public class feign.codec.EncodeException feign.codec.EncodeException: 'Content-Type' cannot contain wildcard type '*'

https://blog.51cto.com/u_15307418/3136335

Method Not Allowed 405

http://www.taodudu.cc/news/show-4499945.html?action=onClick

参考

  1. https://segmentfault.com/a/1190000039889836
  2. https://z.itpub.net/article/detail/8DA760ACC4A1E467592C5B845B18F6D6
  3. https://juejin.cn/post/7021043363386228749
  4. http://www.taodudu.cc/news/show-1503723.html?action=onClick

问题一:通过OpenFeign作为注册中心的客户端时,默认使用Ribbon做负载均衡,Ribbon默认也是用jdk自带的HttpURLConnection,需要给Ribbon也设置一个Http client,比如使用okhttp,在properties文件中增加下面配置:

posted @ 2023-05-25 18:37  易文杰  阅读(360)  评论(0编辑  收藏  举报