feign的使用及原理

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: feign的使用及原理

一:Feign介绍



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


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


Feign支持多种注解,例如Feign自带的注解或者JAX­RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便.

 

二. Ribbon VS Feign


feign和ribbon是Spring Cloud的Netflix中提供的两个实现软负载均衡的组件,Ribbon和Feign都是用于调用其他服务的,方式不同。Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式。


将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求。
不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致

1.启动类使用的注解不同,Ribbon 用的是@RibbonClient,Feign 用的是@EnableFeignClients。


2.服务的指定位置不同,Ribbon 是在@RibbonClient 注解上声明,Feign 则是在定义抽象方法的接口中使用@FeignClient 声明。


3.调用方式不同,Ribbon 需要自己构建 http 请求,模拟 http 请求然后使用 RestTemplate 发送给其他服务,步骤相当繁琐。


个人觉得主要原因是这个


Feign


Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 http 请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。


Ribbon


Ribbon 是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求,步骤相对繁琐。


三. 在工程中引入feign



1. 加入feign依赖

<!-- 服务之间http调用, 引入feign -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2. 添加注解


在启动类增加注解

@EnableFeignClients

3. 修改工程为普通的jar


我们即将创建的这个工程, 专门用来管理维护feign调用请求


因为该工程式一个普通的jar 不需要打可执行的jar


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId> 5     
            <artifactId>maven‐jar‐plugin</artifactId>
        </plugin>
    </plugins>
</build>

4. 编写声明式接口

@FeignClient(name="product‐center")
publicinterfaceProductCenterFeignApi{
  /**
   * 声明式接口,远程调用http://product‐center/selectProductInfoById/{productNo}
   * @param productNo
   * @return
   */
  @RequestMapping("/selectProductInfoById/{productNo}")
  ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo);
}

在order项目中配置


四. 细粒度配置


4.1 设置日志打印级别


默认情况下,Feign的调用式不打印日志,我们需要通过自定义来打印我们的Feign的日志

1187916-20200721045436662-132248612.png

一共有4种级别:


设置日志有2种方式, 一种是配置文件, 一种是实体类. 我们两种方式都试一下


我们来试一下四种日志级别的效果


1. FULL: 记录请求和响应的header,body,元数据


使用实体类的方式


第一步: 自定义一个ProductFeignConfig配置类


/**
 * 这个类上千万不要添加@Configuration,不然会被作为全局配置文件共享 * Created by smlz on 2019/11/22.
 */
class ProductFeignConfig {
    @Bean
    public Logger.Level level() {
        return Logger.Level.FULL;
    }
}

注意: 这个配置类上面不要加@Configuration, 不然会被作为全局配置被所有文件共享


第二步: 在feignClient上指定configuration


@FeignClient(name = "product1", configuration = ProductFeignConfig.class)
public interface ProductClient {
    /**
     * 调用product项目的config接口
     * @return
     */
    @RequestMapping("/config")
    String getconfig();
}

第三步: 设置配置文件,启动调试模式


这里如果不开启, 那么所有日志都不会被打印出. 也就是这里相当于一个开关, 这里设置了, 上面的自定义日志级别才会生效

logging:
  level:
    com:
      lxl:
        www: debug


第四步: 启动zuul-test-order 和zuul-test-product


第五步: 浏览器运行


http://localhost:8081/get/user

1187916-20200721053159148-1449363893.png


以上是日志级别为Full的时候, 打印的日志.


2  HEADERS: 记录basic的基础上, 记录请求和响应的header


这次使用配置文件的方式直接配置


第一步: 注释掉刚才的configuration配置项

@FeignClient(name = "product1" /*, configuration = ProductFeignConfig.class*/)
public interface ProductClient {
    /**
     * 调用product项目的config接口
     * @return
     */
    @RequestMapping("/config")
    String getconfig();
}


注意红色字体, 是删除的部分


第二步: 在调用方: zuul-test-order 通过feign:client:config:微服务名称:loggerLevel: 日志级别来指定


feign:
  client:
    config:
      product1:
        loggerLevel: HEADERS


第三步: 启动zuul-test-order和zuul-test-product


第四步: 浏览器访问url


http://localhost:8081/get/user

1187916-20200721054002543-1091958196.png


咋一看,打印的内容和上面full的差不多, 不过,需要注意的是, 我们这里的这个请求是非常简单的get请求. 当请求的header较多, 请求类型为post的时候, 这里应该是有差别的.

 

3 BASIC: 仅记录请求方法, url, 响应状态码及执行时间(生产环境建议使用)


   NONE: 不打印任何日志


  这里就不一点点记录了, 直接看效果图


  设置为basic

1187916-20200721054849134-2050402226.png


@FeignClient(name="product‐center",configuration=ProductCenterFeignConfig.class)
 public interface ProductCenterFeignApi {
 /**
 * 修改锲约为Feign的 那么就可以使用默认的注解
 * @param productNo
 * @return
 */
 @RequestLine("GET /selectProductInfoById/{productNo}")
 ProductInfo selectProductInfoById(@Param("productNo") String productNo);
}

上述案例使用的是feign如红色重点标出部分, 使用的是RequestLine的方法

通常我们还是使用spring mvc的更多一些, 如下:


@FeignClient(name = "product1" )
public interface ProductClient {
    @RequestMapping("/config")
    String getconfig();
}

4.3 自定义feign拦截器,实现参数的透传


第一步: 自定义拦截器, 该拦截器实现了RequestInterceptor接口

package com.lxl.www.order.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
public class ProductRequestIntercept implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String version = request.getHeader("version");
        requestTemplate.header("version", version);
    }
}

第二步: 启用自定义拦截器


在配置类中添加自定义拦截器


/**
 * 这个类上千万不要添加@Configuration,不然会被作为全局配置文件共享 * Created by smlz on 2019/11/22.
 */
class ProductFeignConfig {
    /*@Bean
    public Logger.Level level() {
        return Logger.Level.FULL;
    }*/
    /*@Bean
    public Contract feignContract() {
        return new Contract.Default();
    }*/
    // 启用自定义拦截器
    @Bean
    public RequestInterceptor getRequestInterceptor() {
        return new ProductRequestIntercept();
    }
}

第三步: 在order项目中写一个接口, 并打印参数version(前三步都是在order中设置)

@GetMapping("version")
    public String getVersion(HttpServletRequest request){
        String version = request.getHeader("version");
        log.info("order获取传递过来的版本号:{}", version);
        productClient.getconfig();
        return this.config;
    }

第四步: 在product项目中接收version

@GetMapping("config")
    public String getconfig(HttpServletRequest request){
        String version = request.getHeader("version");
        log.info("product获取透传过来的版本号:{}", version);
        log.info("调用product/config, {}", 3);
        return this.config;
    }

第五步:查看运行效果

1187916-20200721063417389-231447729.png

1187916-20200721063442354-2048143010.png


我们看到version在两个项目中传递


五. Feign调用优化方案



第一步:开启连接池配置

feign:
  client:
    config:
      product‐center:
        loggerLevel: full
        contract: feign.Contract.Default
        httpclient:
          #让feign底层使用HttpClient去调用
          enabled: true
          max‐connections: 200 #最大连接数 max‐connections‐per‐route: 50 #为每个url请求设置最大连接数

第二步:调整Feign的日志级别(强烈推荐使用Basic级别的)

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
SpringCloud极简入门-Feign开启Hystrix
1.支付服务集成Hystrix 官方文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix 支付服务 springcloud-pay-server-1040 之前集成了Feign,修改该工程集成Hystrix。我们除了要给Feign开启Hystrix以外还需要为Feign接口编写托底类。
191 0
|
设计模式 负载均衡 Nacos
远程调用 OpenFeign 底层原理解析
Feign 是Springcloud 提供一个声明式的伪Http客户端 它使得调用远程服务就像调用本地服务一样简单 只需要创建一个接口 并且添加注解就可以 Nacos 很好的兼容Feign Feign 默认集成了Ribbon 所以在Nacos 下使用Fegin 默认就实现了负载均衡的效果
1772 0
远程调用 OpenFeign 底层原理解析
|
5月前
Feign使用原理
Feign使用原理
132 0
|
7月前
|
负载均衡 Java 应用服务中间件
Ribbon、Feign和OpenFeign的区别来了
Ribbon、Feign和OpenFeign的区别来了
314 2
|
7月前
|
JSON Java 关系型数据库
【Feign】 基于 Feign 远程调用、 自定义配置、性能优化、实现 Feign 最佳实践
【Feign】 基于 Feign 远程调用、 自定义配置、性能优化、实现 Feign 最佳实践
284 0
|
7月前
|
负载均衡 前端开发 Java
openfeign远程调用的底层原理?
openfeign远程调用的底层原理?
|
负载均衡 Dubbo Java
简单理解Feign的原理与使用
简单理解Feign的原理与使用
241 0
|
设计模式 缓存 Dubbo
|
消息中间件 JavaScript 小程序
Spring 6 正式“抛弃”feign
Spring 6 正式“抛弃”feign
|
JSON 负载均衡 算法
9、Eureka、Feign、Ribbon的工作原理及项目实战
在前后端分离架构中,服务层被拆分成了很多的微服务,Spring Cloud中提供服务注册中心来管理微服务信息
190 0
9、Eureka、Feign、Ribbon的工作原理及项目实战