Feign

发布时间 2023-06-20 19:00:18作者: lwx_R

1.概念

Feign是Spring Cloud Netflix组件中的一个轻量级RESTful的HTTP服务客户端,实现了负载均衡和Rest调用的开源框架
封装了Ribbon和RestTemplate,实现了WebService的面向接口编程,进一步降低了项目的耦合度。
Feign内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
Felgn本身并不支持Spring MVC的注解,它有一会自己的注解,为了更方便的使用,Spring Cloud孵化了openFelgn。
Feign是一种声明式、模板化的HTTP客户端(仅在Consumer中使用)。
Feign支持的注解和用法请参考官方文档: htps://github.com/OpenFeign/feign 或spring.io言网文档
Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
Feign旨在使编写JAVA HTTP客户端变得更加容易,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡
使代码变得更加简洁,也少了客户端调用的代码,使用Feign实现负载均衡是首选方案。
只需要你创建一个接口,然后在上面添加注解即可。
Feign是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程HTTP请求。

  • 它解决了让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。无需关注与远程的交互细节,更无需关注分布式环境开发。
  • 它像Dubbo-样, Consumer直接调用Provider接口方法,而不需要通过常规的Http Client构造请求再解析返回数据。
    OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如@RequesMapping 、Pathvariable 等等。
    OpenFeign的@FeignClient 可以解析SpringMVC的@RequestMapping 注解下的接口,并通过动态代理的方式产“生实现
    类,实现类中做负载均衡并调用服务。
  • 读作 fei en

2.实现

  • 服务中心和提供者一致
  • pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  • 生产者中的ProductService
@FeignClient("provider")
public interface ProductService {
    @GetMapping("/product/list")
    List<Product> listProducts();
}

3.Feign负载均衡

Feign封装了Ribbon自然也就集成了负载均衡的功能,默认采用轮询策略。如何修改负载均衡策略呢?与之前学习Ribbon时讲解的配置是一致的。

4.参数请求

  • 消费者的ProductSercice
@FeignClient("provider")
public interface ProductService {

    @GetMapping("/product/{id}")
    Product selectProductById(@PathVariable("id") Integer id);

    @PostMapping("/product/save")
    Map<Object, Object> createProduct(Product product);
}
  • 生产者的ProductController
@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product selectProduct(@PathVariable("id") Integer id){
        return productService.selectProductById(id);
    }

    @PostMapping("/save")
    public Map<Object, Object> createProduct(@RequestBody Product product){
        return productService.createProduct(product);
    }
}

5.Feign性能优化

5.1 Gzip压缩

gzip介绍: gzip 是-种数据格式,采用deflate 算法压缩数据; gzip 是-种流行的文件压缩算法,应用十分广泛,尤其是在Linux平台。
gzip能力:当Gzip压缩一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。
gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。
网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是Gzip与搜索引擎的抓取工具有着更好的关系。
例如Google就可以通过直接读取gzip文件来比普通手工抓取更快地检索网页。

5.1.1 HTTP协议关于压缩传输的规定

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

5.1.2 实现
  • 局部
    只配置Consumer通过Feign到Provider的请求与相应的Gzip压缩。服务消费者application.yml
server:
  port: 9090
  compression:
    #开启压缩
    enabled: true
    #压缩支持的 MINME TYPE
    mime-types: application/json,application/xml,text/html,text/xml,text/plain
  • 全局
    对客户端浏览器的请求以及Consumer对Provider的请求与响应都实现Gzip压缩。
    服务消费者applicationyml
feign:
  compression:
    request:
      mime-types: text/xml. application/xn1. application/json #配置压缩支持的MIME TYPE
      min-request-size: 512 #配置压缩数据大小的最小阈值,默认2048
      enabled: true #请求是否开启gzip压缩
    response:
      enabled: true #响应是否开启gzip压缩

5.2 HTTP连接池

5.2.1HTTP的背景原理

  • 两台服务器建立HTTP连接的过程是很复杂的一个过程,涉及到多个数据包的交换,很耗时间。
  • HTTP连接需要的3次握手4次挥手开销很大,这一 开销对于大量的比较小的HTTP消息来说更大。

5.2.2 解决方案

采用HTTP连接池,可以节约大量的3次握手4次挥手,这样能大大提升吞吐量。
Feign的HTTP客户端支持3种框架: HttpURLConnection,HttpClient,OkHttp;默认是HttpURLConnection 。

  • 传统的HttpURLConnection是JDK自带的,并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。
    对于网络请求这种底层相对复杂的操作,如果有可用的其他方案,没有必要自己去管理连接对象。
  • HttpClient 相比传统JDK自带的HttpURLConnection,它封装了访问HTTP的请求头,参数,内容体,响应等等
    它不仅使客户端发送HTTP请求变得容易,而且也方便了开发人员测试接口(基于HTTP协议的) , 既提高了开发的效率,又提高了代码的健壮性;
    另外高并发大量的请求网络的时候,也是用连接池提升吞吐量。

5.2.3 实现

#httpclient开启
feign:
  httpclient:
    enabled: true

5.3 状态查看

浏览器发起的请求我们可以借助F12 Devtools 中的Network来查看请求和响应信息。
对于微服务中每个接口我们又该如何查看URL,状态码和耗时信息?我们可以使用配置日志的方式进行查看。

5.3.1 实现

  • logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/home" />

    <!--控制台日志, 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE"/>
    </root>
</configuration>
  • 启动类加入
/**
     * NONE: 不记录任何信息
     * BASIC 记录请求方法,URL,状态码,用时
     * HEADERS BASIC基础上记录一些常用信息
     * FULL 记录请求相关所有信息
     * @return
     */
    @Bean
    public Logger.Level getLog(){
        return Logger.Level.FULL;
    }
  • application.yml
#httpclient开启
feign:
  httpclient:
    enabled: true
  client:
    #服务名称
    provider:
      loggerLevel: FULL

5.4 请求超时

Feign的负载均衡底层用的就是Ribbon,所以这里的请求超时配置其实就是配置Ribbon.
分布式项目中,服务压力比较大的情况下,可能处理服务的过程需要花费一定的时间, 而默认情况下请求超时的配置是1s所以我们需要调整该配置延长请求超时时间。

5.4.1 实现

  • 局部
provider:
  ribbon:
    NFLoadBalanceRuleClassName: com.netflix.loadbalancer.RandomRule
    #所有请求都进行重试
    OkToRetryOnAllOperations: true
    #对当前实例重试次数
    MaxAutoRetries: 2
    #切换实例的重试次数
    MaxAutoRetriesNextServer: 0
    #请求连接的超时时间
    ConnectTimeout: 3000
    #请求处理的超时时间
    ReadTimeout: 3000
  • 全局
ribbon:
  #请求连接超时时间
  ConnectTimeout: 5000
  #请求处理的超时时间
  ReadTimeout: 5000