Spring Cloud Feign 整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix 的功能,提供了一种声明式的 Web 服务客户端定义方式。它在 Open Feign 的基础上扩展了对 Spring MVC 的注解支持。

构建 Feign 客户端

1. 创建工程

使用 Spring Initializr 生成一个 Spring Cloud Feign 的 Maven 项目

  • Group:com.yjw.springcloudfeignconsumer
  • Artifact:spring-cloud-feign-consumer
  • Dependencies:
    • Spring Boot Actuator
    • Spring Web Starter
    • OpenFeign
    • Ribbon
    • Eureka Discovery Client

注意:

  • Ribbon 必须要引入,否则控制台会报错。
  • Eureka Discovery Client 也必须要引入,否则该服务不会注册到服务注册中心。

2. 添加 @EnableFeignClients 注解

主类 Application 添加 @EnableFeignClients 注解,开启 Spring Cloud Feign 的支持功能。

  1. @EnableFeignClients
  2. @EnableDiscoveryClient
  3. @SpringBootApplication
  4. public class Application {
  5. public static void main(String[] args) {
  6. SpringApplication.run(Application.class, args);
  7. }
  8. }

3. 服务调用

定义服务接口,通过 @FeignClient 注解指定服务名来绑定服务,然后再使用 Spring MVC 的注解来绑定具体服务提供的 REST 接口。

  1. /**
  2. * eureka-provider 是服务名,不区分大小写
  3. */
  4. @FeignClient("eureka-provider")
  5. public interface ProviderClient {
  6. @GetMapping("/user/{id}")
  7. void getUserById(@PathVariable("id") Long id);
  8. @GetMapping("/user/getUserByName")
  9. void getUserByName(@RequestParam("name") String name);
  10. @GetMapping("/user/getUser")
  11. void getUser(@SpringQueryMap UserQuery userQuery);
  12. @PutMapping("/user/update")
  13. void updateUser(UserQuery userQuery);
  14. @PostMapping("/user/add")
  15. void addUser(UserQuery userQuery);
  16. @DeleteMapping("/user/{id}")
  17. void deleteUserById(@PathVariable("id") Long id);
  18. }

注意:这里的服务名不区分大小写。

4. 配置

application.properties 配置内容和服务提供者差不多,只需要修改一下应用名称和端口号。

  1. spring.application.name=feign-consumer
  2. server.port=7001
  3. logging.level.root=info
  4. #打开shutdown
  5. management.endpoint.shutdown.enabled=true
  6. #支持HTTP请求
  7. management.endpoints.web.exposure.include=*
  8. eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9000/eureka/,http://localhost:9001/eureka/

5. 测试验证

项目地址:https://gitee.com/yin_jw/demo/tree/master/springcloud-demo/spring-cloud-ribbon-consumer

我们先启动服务注册中心以及两个 eureka-provider 服务,然后启动 feign-consumer 服务,查看 Eureka 信息面板可以看到各个服务的启动情况。

image.png

使用 Postman 测试接口,可以在服务提供者控制台看到接口输出。我们可以看到使用 Feign 实现的消费者,依然是利用 Ribbon 维护了针对 eureka-provider 的服务列表信息,并且实现了客户端负载均衡。

参数绑定

Spring Cloud Feign 的参数绑定和 Spring MVC 类似,注解的使用可以参考 Spring MVC 的使用,这里只需要注意一下 /user/getUser 接口的 @SpringQueryMap 注解,该注解为 POJO 提供了支持,可用作 GET 参数映射。

简单的参数绑定使用在 spring-cloud-feign-consumer 项目中有具体的示例,可作为参考。

Ribbon 配置

由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以通过配置 Ribbon 的配置来自定义客户端调用的参数。

  1. #全局配置
  2. ribbon.ConnectTimeout=500
  3. ribbon.ReadTimeout=5000
  4. #指定服务配置
  5. eureka-provider.ribbon.ConnectTimeout=500
  6. eureka-provider.ribbon.ReadTimeout=2000
  7. #重试机制
  8. eureka-provider.ribbon.OkToRetryOnAllOperations=false
  9. eureka-provider.ribbon.MaxAutoRetries=0
  10. eureka-provider.ribbon.MaxAutoRetriesNextServer=1

全局配置

使用 ribbon.= 的方式来设置 Ribbon 的各项默认参数。比如,修改默认的客户端调用超时时间:

  1. ribbon.ConnectTimeout=500
  2. ribbon.ReadTimeout=5000

指定服务配置

使用 .ribbon.= 的方式来设置指定服务的参数。在定义 Feign 客户端的时候,使用 @FeignClient 注解。在初始化过程中,Spring Cloud Feign 会根据该注解的 name 属性或 value 属性指定的服务名,自动创建一个同名的 Ribbon 客户端,所以这里 指的就是服务名。比如:

  1. eureka-provider.ribbon.ConnectTimeout=500
  2. eureka-provider.ribbon.ReadTimeout=2000

重试机制配置

Spring Cloud Feign 默认实现了请求的重试机制,下面的配置可以设置 Feign 的重试机制。

  1. eureka-provider.ribbon.OkToRetryOnAllOperations=false
  2. eureka-provider.ribbon.MaxAutoRetries=0
  3. eureka-provider.ribbon.MaxAutoRetriesNextServer=1
  • eureka-provider.ri**bbon.OkToRetryOnAllOperations**:对所有操作请求都进行重试。默认值为 false,只对 GET 请求进行重试。该参数的使用源码参考 RibbonLoadBalancedRetryPolicy#canRetry 方法。
  • eureka-provider.ribbon.MaxAutoRetriesNextServer:切换实例的重试次数,不包括首次调用。
  • eureka-provider.rib**bon.MaxAutoRetries**:对当前实例的重试次数,不包括首次调用。

注意:Ribbon 的超时与 Hystrix 的超时是两个概念。为了可以实现 Feign 的重试机制,需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。

Hystrix 配置

全局配置

默认情况下,Spring Cloud Feign 会将所有 Feign 客户端的方法都封装到 Hystrix 命令中进行服务保护。

  1. #全局配置,超时时间
  2. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

服务降级配置

  1. 服务降级逻辑的实现只需要为 Feign 客户端的定义接口编写一个具体的接口实现类。其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑。
  1. /**
  2. * 服务降级
  3. */
  4. @Component
  5. public class ProviderFallback implements ProviderClient {
  6. @Override
  7. public void getUserById(Long id) {
  8. System.out.println("getUserById 服务降级");
  9. }
  10. @Override
  11. public void getUserByName(String name) {
  12. System.out.println("getUserByName 服务降级");
  13. }
  14. @Override
  15. public void getUser(UserQuery userQuery) {
  16. System.out.println("getUser 服务降级");
  17. }
  18. @Override
  19. public void updateUser(UserQuery userQuery) {
  20. System.out.println("updateUser 服务降级");
  21. }
  22. @Override
  23. public void addUser(UserQuery userQuery) {
  24. System.out.println("addUser 服务降级");
  25. }
  26. @Override
  27. public void deleteUserById(Long id) {
  28. System.out.println("deleteUserById 服务降级");
  29. }
  30. }
  1. 在服务绑定接口 ProviderClient 中,通过 @FeignClient 注解的 fallback 属性来指定对应的服务降级实现类。
  1. @FeignClient(name ="eureka-provider", fallback = ProviderFallback.class)
  2. public interface ProviderClient {
  3. ......
  4. }

日志配置

Spring Cloud Feign 在构建被 @FeignClient 注解修饰的服务客户端时,会为每一个客户端都创建一个 feign.Logger 实例,可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。可以在 application.properties 文件中使用 logger.level. 的参数配置格式来开启指定 Feign 客户端的 DEBUG 日志,其中 为 Feign 客户端定义接口的完整路径。比如:

  1. logging.level.com.yjw.springcloudfeignconsumer.manager.client.ProviderClient=debug

只是添加了如上配置,还无法实现对 DEBUG 日志的输出。这是由于 Feign 客户端默认的 Logger.Level 对象定义为 NONE 级别,该级别不会记录任何 Feign 调用过程中的信息,所以我们需要调整它的级别,针对全局的日志级别,可以在应用主类中直接加入 Logger.Level 的Bean 创建,具体如下:

  1. @EnableFeignClients
  2. @EnableDiscoveryClient
  3. @SpringBootApplication
  4. public class Application {
  5. public static void main(String[] args) {
  6. SpringApplication.run(Application.class, args);
  7. }
  8. @Bean
  9. Logger.Level feignLoggerLevel() {
  10. return Logger.Level.FULL;
  11. }
  12. }

也可以通过实现配置类,然后再具体的 Feign 客户端来指定配置类以实现是否要调整不同的日志级别,比如下面的实现:

  1. @Configuration
  2. public class FullLogConfiguration {
  3. @Bean
  4. Logger.Level feignLoggerLevel() {
  5. return Logger.Level.FULL;
  6. }
  7. }
  8. @FeignClient(name ="eureka-provider", configuration = FullLogConfiguration.class)
  9. public interface ProviderClient {
  10. ......
  11. }

在调整日志界别为 FULL 之后,我们再次访问 http://localhost:7000/user/1 接口,这时我们在 feign-consumer 的控制台中就可以看到如下详细日志:

  1. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] ---> GET http://eureka-provider/user/1 HTTP/1.1
  2. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] ---> END HTTP (0-byte body)
  3. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] <--- HTTP/1.1 200 (16ms)
  4. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] content-length: 0
  5. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] date: Wed, 19 Jun 2019 02:24:48 GMT
  6. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById]
  7. DEBUG 23580 --- [nio-7000-exec-3] c.y.s.manager.client.ProviderClient : [ProviderClient#getUserById] <--- END HTTP (0-byte body)
  8. INFO 23580 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration

对于 Feign 的 Logger 级别主要有下面4类,可以根据实际需要进行调整使用。

  • NONE:不记录任何信息。
  • BASIC:仅记录请求方法、URL 以及相应状态码和执行时间。
  • HEADERS:除了记录 BASIC 级别的信息之外,还会记录请求和相应的头信息。
  • FULL:记录所有请求与相应的明细,包括头信息、请求体、元数据等。