天天看点

SpringCloud【OpenFeign】-服务间调用的利器

SpringCloud【OpenFeign】-服务间调用的利器

文章目录

  • ​​前言​​
  • ​​一、OpenFeign快速使用​​
  • ​​1.1 SpringCloud Alibaba快速整合OpenFeign​​
  • ​​二、声明式服务调用​​
  • ​​三、编程式服务调用:fire::fire:​​
  • ​​【好处】​​
  • ​​四、OpenFeign相关配置​​
  • ​​4.1 日志配置​​
  • ​​4.1.1 全局配置日志​​
  • ​​4.1.2 局部配置​​
  • ​​4.2 超时时间配置​​
  • ​​4.2.1 全局配置超时时间​​
  • ​​4.2.2 局部配置​​
  • ​​4.3 自定义拦截器​​
  • ​​4.4 熔断降级处理​​
  • ​​4.4.1 FallBackFactory​​

前言

在学习OpenFeign之前,我们有必要了解下​

​什么是Feign​

​❓,好多人老把这两个东西混为一谈。认为他们是一个东西。

  • ​Feign:​

    ​​Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。​

    ​也就是说Feign有缺陷,所以OpenFeign就产生了。​

  • -​

    ​Openfeign:​

    ​​是Spring CLoud的二级子项目,是基于Feign的基础上进行了完善和优化,​

    ​支持了SpringMVC的注解​

    ​​,​

    ​我们可以像写控制层的转发一样完成服务与服务之间的调用。​

Feign现在已经不再更新维护,所以目前基本上都是使用OpenFeign

介绍完Feign和OpenFeign的区别后,那么我可以回想下以前我们调用服务是怎么样调用的?----​

​RestTemplate​

使用RestTemplate我们需要在控制层不断地维护请求服务的地址,参数,返回值。很不优雅,代码阅读也十分繁琐。不好,不喜欢!!!

❓那么有没有一种更加简便且优雅的方式调用服务呢-----那就是接下来要学习的OpenFeign了。

OpenFeign也是放在服务消费端的,也整合了负载均衡器。

一、OpenFeign快速使用

1.1 SpringCloud Alibaba快速整合OpenFeign

【父级pom】

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.11.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.cloud-version>Hoxton.SR8</spring.cloud-version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- springCloud -->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring.cloud-version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- springcloud alibaba -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.5.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- springBoot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- json -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
  </dependencies>      

二、声明式服务调用

所谓的声明式,就是需要利用注解声明一个调用服务的接口方法,这个方法必须声明在服务消费方。使用@FeignClient声明

【实现步骤】

  1. 引入依赖
<!--OpenFeign-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>      
  1. 在服务消费者端,写一个调用服务的接口
@Component
@FeignClient(name = "stock-service",path = "/stock")
public interface FeignStockService {
  
  @RequestMapping("/reduct")
  public String name();
  
}      
  • @FeignClient:指明需要调用的服务的名称以及路径和失败回调,这个注解​

    ​会扫描@EnableFeignClients指定的包​

    ​​;如果未指定,​

    ​则会找@FeignClient指定的服务名,在注册中心找对应的服务IP万层服务调用​

  • name:​

    ​指定FeignClient的名称​

    ​,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • path:​

    ​定义当前FeignClient的统一前缀​

    ​,当我们项目中配置了server.context-path,server.servlet-path时使用
  • fallbackFactory:​

    ​工厂类,用于生成fallback类示例​

    ​,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • fallback:​

    ​定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口​

  • configuration:​

    ​Feign配置类​

    ​,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
  • url:​

    ​ url一般用于调试​

在写FeignClients的时候,我们可以找一个规律,就是这里的接口方法实际就是从服务提供者的controller里粘贴过来的方法,如下图所示:
SpringCloud【OpenFeign】-服务间调用的利器
SpringCloud【OpenFeign】-服务间调用的利器
  1. 控制层实现调用
@RestController
@RequestMapping("/order")
public class OrderController {

  @Autowired
  private FeignStockService feignStockService;

  @RequestMapping("/add")
  public String add() {
    System.out.println("下单成功!");
    return "hello Feign" + feignStockService.name();
  }
}      

使用了OpenFeign后,我们在也不需要向原来使用RestTemplate一样在写很多遍的url,去获取服务。很方便,但是又有一个新的问题❓

就是我们又要去写很多遍的服务接口方法—这样耦合度还是很高,还不够优雅—接着改

  1. 在启动类上加上​

    ​@EnableFeignClients​

    ​注解,表示自动开启Feign远程调用。
@SpringBootApplication
@EnableDiscoveryClient // nacos版本在1.4以后就可以不用添加 表示nacos启动客户端
@EnableFeignClients // 开启Feign调用
public class OrderApplication {

  public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
  }
}      

三、编程式服务调用🔥🔥

我们可以发现,虽然OpenFeign已经使得我们在调用服务的时候简化了一些冗余操作,但是又引出了一些新的问题。所以我们在公司开发项目的时候,是不太可能会使用上述的方式实现,最佳实践如下操做:

【实现步骤】

  1. 在创建一个Maven模块,用于封装定义的接口API方法
SpringCloud【OpenFeign】-服务间调用的利器

【好处】

​这样做的目的就是实现解耦合​

​,将公共的feign的调用接口抽取成一个公共的模块,通过GAV引入到服务提供者和服务消费者的模块下,这样就可以实现服务间的调用通信。更加优雅
  1. 在公共的接口模块里,只需要写一个接口,将原来写在服务消费端的接口定义子这里
/**
 * 定义公共接口模块
 * 类似于之前的服务接口一样,只不过在微服务里面我们是通过模块 引入gav实现调用
 * 原理大同小异
 * @author wangruoxian
 *
 */
public interface CommonStockApi {
  
  @RequestMapping("/reduct")
  public String name();
  
}      
到这里,我们需要将它maven install以下生成对应的jar包。方便其他模块引用
  1. 在服务提供这里的控制层实现此接口

先引入公共接口的坐标

<dependency>
  <groupId>com.wei</groupId>
  <artifactId>DHC_SpringCloud_CommonOrderAPI</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>      

两个服务的提供者都是需要实现接口

@RestController
@RequestMapping("/stock")
public class Stock8011Controller implements CommonStockApi{
  
  @Value("${server.port}")
  String port;
  
  @RequestMapping("/reduct")
  public String name() {
    System.out.println("库存减少");
    return "减扣库存"+port;
  }
}      
  1. 最后在服务的消费者端的接口就只需要继承公共接口,而不要声明定义。

​这样就将服务消费者与提供者之间的耦合度消除了。​

@Component
@FeignClient(name = "stock-service",path = "/stock")
public interface FeignStockService extends CommonStockApi{}      

四、OpenFeign相关配置

OpenFeign提供了很多的扩展机制,让用户可以更加灵活的使用。

4.1 日志配置

有的时候我们遇到BUG,比如接口调用失败,参数有没有问题,或者想看调用的性能。这个时候,就需要使用Feign日志了,一次让Feign把请求信息输出出来。

【源码查看】

public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }      

通过查看OpenFeign的源码,我们发现OpenFeign提供了四种的日志级别:

  • NONE:【性能最佳,仅用于生产】不输出任何的日志信息(默认)
  • BASIC:【用于生产环境追踪问题】仅记录一些请求方法,URL,响应状态码以及执行时间
  • HEADERS:记录BASIC级别基础上,记录请求和响应的Header。
  • FULL:【比较适合用于开发及测试环境定位问题】:记录请求和相应的header、body以及元数据。

​那么我们该如何实现呢???​

  • 全局配置:针对所有的服务
  • 局部配置:仅针对某个服务

4.1.1 全局配置日志

我们需要定义一个配置类,实现日志的全局配置

@Configuration
public class MyFeignConfig {

  @Bean
  public Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
  }
}      
加上了@Configuration注解之后,就代表这是一个全局配置类

然后,我们还需要再yml文件里声明一下它是以DEBUG的模式输出打印的,否则你正常启动他是不显示的

logging:
  level:
    com.wei.feign.api:      
SpringCloud【OpenFeign】-服务间调用的利器

4.1.2 局部配置

【编码的方式配置局部日志】

要使用局部配置就不需要在写@Configuration注解,只需要在@feignClient里声明​

​configuration​

​即可

/**
 * 指定输出Product服务的日志信息
 * @author wangruoxian
 *
 */
@FeignClient(name = "server-product",path = "/product",configuration = MyFeignConfig.class)
public interface FeignProductService extends CommonProductApi {}      
SpringCloud【OpenFeign】-服务间调用的利器

这里仅有product的日志信息

补充: 局部配置还可以写在yml配置文件里
feign:
  client:
    config:
      server-product: # 对应的服务名称
        logger-level:      
SpringCloud【OpenFeign】-服务间调用的利器
这里很明显的观察到basic的日志输出没有full的全,仅有一些请求的信息,没有响应的信息

4.2 超时时间配置

通过Options可以配置连接超时时间和读取超时时间,Options的第一个参数是连接的超时时间(ms),默认值是2s;第二个参数是请求的超时时间,,默认是5s。

4.2.1 全局配置超时时间

@Configuration
public class FeignConfig(){
  @Bean
  public Request.Options options(){
    return new Request.Options(5000,10000);
  }
}      

4.2.2 局部配置

方式一还是和配置日志的方式一样,这里省略

方式二:通过yml文件配置,​​

​服务消费者yml​

# 局部配置
feign:
  client:
    config:
      server-product: # 对应的服务名称
        logger-level: basic
        connect-timeout: 5000 # 连接超时 默认是2s
        read-timeout: 3000 # 请求超时时间 默认是5s      

然后我们模拟请求超时,让Product服务线程sleep4s后,在启用,这里很明显就会报请求异常的错误

@RequestMapping("/get/{id}")
  public String get(@PathVariable("id") Integer id) {
    try {
      Thread.sleep(4000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("查询商品"+id);
    return "查询商品"+id+":"+port;
  }      
SpringCloud【OpenFeign】-服务间调用的利器

4.3 自定义拦截器

自定义拦截器需要实现RequestInterceptor 接口,通过Resttemplate完成对服务调用的拦截操做,

​​

​这里比如拦截/product/get/{id}这个请求 并打印info级别的日志​

public class CustomFeignInterceptor implements RequestInterceptor {

  Logger logger = LoggerFactory.getLogger(this.getClass());

  @Override
  public void apply(RequestTemplate template) {
    // TODO Auto-generated method stub
//    template.header("xxx", "xxx");
    template.uri("/get/9");
    logger.info("feign的自定义拦截器");
  }

}      

使用局部配置自定义拦截器,使之生效

# 局部配置
feign:
  client:
    config:
      server-product: # 对应的服务名称
        logger-level: basic
        connect-timeout: 10000 # 连接超时 默认是2s
        read-timeout: 10000 # 请求超时时间 默认是5s
        # 局部配置自定义拦截器
        request-interceptors:
        -      
SpringCloud【OpenFeign】-服务间调用的利器

4.4 熔断降级处理

openFeign实际上是已经引入了hystrix的相关jar包,所以可以直接使用,设置超时时间,超时后调用FallBack方法,实现熔断机制。

首先在消费者工程添加Maven依赖。

<!--OpenFeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--不能引入hystrix的依赖 原因在开头已经说明-->
<!--
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
-->      
使用​

​bootstrap.yaml ​

​启用 hystrix 断路器,Feign 将使用断路器包装所有方法
  • Feign 开启 hystrix 断路器feign.hystrix.enabled=true
  • Feign 关闭 spring cloud 断路器 feign.circuitbreaker.enabled=true
feign:
  hystrix:
    enabled: true
  circuitbreaker:
    enabled: false      

​注意:启动类不能添加 @EnableCircuitBreaker ,如果添加该注解需要导入 spring-cloud-starter-netflix-hystrix 依赖​

@SpringBootApplication
@EnableDiscoveryClient // nacos版本在1.4以后就可以不用添加 表示nacos启动客户端
@EnableFeignClients
public class OrderApplication {

  public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
  }
}      
注意:服务降级有两种实现方式:
  • fallback:消费者端本地实现远程接口,​

    ​即fallback形式​

    ​​,​

    ​此方式相对简单方便,但无法处理异常​

    ​,因此个人不推荐此种方式实现的服务降级
  • fallbackfactiory:​

    ​消费者端本地实现 FallbackFactory<>接口​

    ​​,即fallbackFactory形式,此方式也比较简单,​

    ​并且可以处理异常,推荐此方式实现。​

4.4.1 FallBackFactory

在标注了FeignClient的标注的接口上使用fallbackFactory=“自定义的失败回调工厂类”

@FeignClient(
      name = "server-product", 
      path = "/product",
      fallbackFactory = ProductFallBackFactory.class)
public interface FeignProductService extends CommonProductApi {}      

自定义的回调工厂类ProductFallBackFactory

@Component
public class ProductFallBackFactory implements FallbackFactory<FeignProductService>{

  @Override
  public FeignProductService create(Throwable cause) {
    // TODO Auto-generated method stub
    return new FeignProductService() {
      
      @Override
      public String get(Integer id) {
        // TODO Auto-generated method stub
        return "服务调用失败";
      }
    };
  }
}      

​注意:​

​ 自定义的FallBackfactory类所要实现的​

​FallbackFactory​

​的泛型必须要约定为提供接口服务的@FeignClient接口,然后通过retuen返回自定义的返回值,这样当服务调用出现错误就会将return的结果返回,而不会使服务中断崩溃。

测试

这里我们为了方柏测试,将preoduct服务sleep5s

@RestController
@RequestMapping("/product")
public class ProductController implements CommonProductApi{
  
  @Value("${server.port}")
  private String port;

  @RequestMapping("/get/{id}")
  public String get(@PathVariable("id") Integer id) {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("查询商品"+id);
    return "查询商品"+id+":"+port;
  }
}