Feign 客户端负载均衡

摘要:Feign 是由 Netflix 开源的声明式的 HTTP 客户端,通过使用定义简单的接口,并声明 Feign 提供的注解,来实现 HTTP 的调用。


目录

[TOC]

Feign

Feign 是由 Netflix 开源的声明式的 HTTP 客户端,目前已经捐献给 OpenFeign 社区。

  • 通过使用定义简单的接口,并声明 Feign 提供的注解,来实现 HTTP 的调用。

Spring Cloud OpenFeign

Spring Cloud OpenFeign,将 Feign 集成到 Spring Cloud 体系中,实现服务的声明式 HTTP 调用

  • 相比使用 RestTemplate 实现服务的调用,Feign 简化了代码的编写,提高了代码的可读性,大大提升了开发的效率。

  • 可插拔的注解支持,包括FeignJAX-RSSpringMvc注解。
    1. 除了支持 Feign 自带的注解之外,额外提供了对 JAX-RS 注解、SpringMVC 注解的支持。
    2. 特别是对 SpringMVC 注解的支持,简直是神来之笔,让我们不用学习 Feign 自带的注解,而直接使用超级熟悉的 SpringMVC 注解。
  • 同时,Spring Cloud OpenFeign 进一步将 Feign 和 Ribbon 整合(和Eureka?),提供了负载均衡的功能。另外,Feign 自身已经完成和 Hystrix 整合,提供了服务容错的功能。可以不再需要显式地使用这两个组件。

如此,基于注解,极其简单的实现服务的调用,并且具有负载均衡、服务容错的功能。

用法:

  • Feign是声明式的服务调用工具,只需创建一个接口、并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。
    • 提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求参数、格式、地址等信息。
    • Feign会完全代理HTTP请求,开发时只需要像调用方法一样调用它就可以完成服务请求及相关处理。

另外:

  • 支持可插拔的HTTP编码器和解码器;
  • 支持HTTP请求和响应的压缩

可设计一套稳定可靠的弹性客户端调用方案,避免整个系统出现雪崩效应

将Feign视为Spring RestTemplate使用接口与endpoints进行通信。

  • 该接口将在运行时自动实现,不是使用服务URL地址,而是使用服务名称

Feign 与 RestTemplate 的对比

  • 从开发效率、可维护性的角度来说,Feign 更加有优势。

  • 从执行性能、灵活性的角度来说,RestTemplate 更加有优势。

个人推荐使用 Feign 为主,RestTemplate 为辅

  • 相比来说,开发效率、可维护性非常重要,要保证开发的体验。
  • 执行性能的问题,因为 Feign 多一层 JDK 动态代理,所以会差一些。不过 HTTP 调用的整体性能的大头在网络传输和服务端的执行时间,所以 Feign 和 RestTemplate 的性能差距可以相对忽略。
  • 灵活性的问题,99.99% 的情况下,Feign 都能够实现或者相对绕的实现;无法实现的情况下,在考虑采用 RestTemplate 进行实现。

Spring Boot 独立集成 Feign

纯 Spring Boot 环境下,如何使用 Feign 框架。

基本所有的网上文章,都是通过 Spring Cloud Netflix Feign 进行 Feign 的使用,这样就引入了大量 Spring Cloud 的依赖。而我们的希望,可能仅仅只想使用 https://github.com/OpenFeign/feign 核心库。

  • 原因是,采用 Spring Boot + Dubbo 实现微服务架构,同时希望使用 Feign 作为 HTTP 客户端调用外部的三方服务,而不再直接使用 HttpClient、RestTemplate 等等。

Feign Client

如果没有Feign,

  • 将不得不将EurekaClient的一个实例自动连接到控制器中,
  • 通过该实例可以接收一个以service-name命名的服务信息、作为Application对象。
  • 根据此对象获取(该服务的)所有实例的列表,选择一个合适的实例,然后使用此实例获取主机名和端口。
  • 这样,可以对任何http客户端发出标准请求。

服务器端,可以将它们实现为@Controller,而在客户端,可以将其扩展和注解为@FeignClient

  • Feign客户端:位于spring-cloud-starter-feign软件包中。
    1. 通过在主程序类上加 @EnableFeignClients注解来启用。
    2. 要使用它,只需使用@FeignClient("service-name")注解一个接口,然后将其自动连接到控制器中即可。
      • 创建此类Feign客户端的一种好方法是使用@RequestMapping注解方法创建接口,并将其放入单独的模块中。 这样,它们可以在服务器和客户端之间共享。

api 包:

img

User.java 放在 api 包中:

  • 好处:方便保证服务生产者和消费者使用同一个 UserDTO 实体类。

img

搭建步骤

搭建一个 Spring Cloud OpenFeign 组件的快速入门示例。步骤如下:

  1. 首先,搭建一个服务提供者 demo-provider,使用 Spring MVC 注解 @RestController 提供 RESTful HTTP 接口。
    • 启动 2 个实例,注册服务到 Nacos 中。用于测试 Ribbon 负载均衡。
  2. 然后,搭建一个服务消费者 demo-consumer,使用 Ribbon 进行负载均衡,使用 Feign 声明式调用服务提供者 demo-provider 的 HTTP 接口。

继承特性

Spring Cloud OpenFeign 提供了 SpringMVC 注解的支持,所以可以将服务提供者 Controller 提取出一个接口(即 -api),让服务提供者和消费者共同使用

这就是 Spring Cloud OpenFeign 提供的继承特性

Feign 继承特性示例

结构

  • -rpc-service 包:搭建服务提供者 API 项目:提供服务提供者的 API 接口DTO 类。
    • api 包:提供服务提供者的 API 接口。
    • dto 包:提供数据传输对象 DTO 类。封装复杂参数。
  • -privider / -rpc-service-impl / -server 包:
    • apiImpl 包:实现 API 接口。
    • controller
    • service

实践建议

在 Spring Cloud OpenFeign 官方文档不推荐使用继承特性

  • 因为通过 Java 接口的共享,导致服务提供者和消费者的耦合,而微服务的目的是为了服务提供者和消费者的解耦,存在一定的冲突。

不过实际场景下,蛮多公司采用继承特性,显而易见的好处,可以方便服务消费者的快速接入,基本无需编写额外的代码。

具体怎么选择,可以自己进行评估,看看使用继承特性的情况下,在享受优点的同时,是否能够接受带来的缺点。

  • 个人意见的话,是支持采用继承特性
  • Dubbo 的使用方式来说,也可以认为它是支持采用继承特性

搭建服务提供者

依赖、配置

将 Nacos 作为注册中心,并实现对其的自动配置。

ProviderController

创建 ProviderController 类,提供 HTTP 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class ProviderController {

    private Logger logger = LoggerFactory.getLogger(ProviderController.class);

    @Value("${server.port}")
    private Integer serverPort;

    @GetMapping("/echo")
    public String echo(String name) throws InterruptedException {
        // 模拟执行 100ms 时长。方便后续我们测试请求超时
        Thread.sleep(100L);

        // 记录被调用的日志
        logger.info("[echo][被调用啦 name({})]", name);

        return serverPort + "-provider:" + name;
    }
    
    @GetMapping("/get_demo")
    public DemoDTO getDemo(DemoDTO demoDTO) {
        return demoDTO;
    }

    @PostMapping("/post_demo")
    public DemoDTO postDemo(@RequestBody DemoDTO demoDTO) {
        return demoDTO;
    }
}

DemoProviderApplication

创建 DemoProviderApplication 类,创建应用启动类。

搭建 feign-service 客户端(服务消费者)

这里创建一个feign-service模块来演示feign的常用功能。

使用 Feign 声明式调用服务提供者 demo-provider 的 HTTP 接口。

依赖

主要引入 Spring Cloud Nacos Discovery + Spring Cloud OpenFeign 相关依赖。

  • 这里没有主动引入 spring-cloud-netflix-ribbon 依赖,因为 spring-cloud-starter-alibaba-nacos-discoveryspring-cloud-starter-openfegign 默认都引入了它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中心,并实现对其的自动配置 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

自定义配置

例如说,自定义 Feign 的日志配置,将 Feign 的请求信息打印出来,方便排查问题。

在自定义 Feign 配置的时候,会有全局客户端两种级别。

  • 相比来说,客户端级别是更细粒度的配置。针对每个服务,Spring Cloud OpenFeign 会创建一个 Feign 客户端,并且使用服务名作为 Feign 客户端的名字

实现 Feign 自定义配置,可以通过配置文件Spring JavaConfig 两种方式。

yml 配置

总结来说,这里配置名字为 demo-provider 的 Feign 客户端的日志级别为 FULL,全局级别的 Feign 客户端的日志级别为 BASIC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server:
  port: 8701
spring:
  application:
    name: feign-service
    
# 注册中心
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/

feign:
  # Feign 客户端配置,对应 FeignClientProperties 配置属性类
  client:
    # config 配置项可以设置每个 Feign 客户端的配置,是 Map 类型。
    # key 为 Feign 客户端的名字,value 为 FeignClientConfiguration 对象
    config:
      # `default` 为特殊的 *key*,用于全局级别的配置。
      default:
        # 设置 Feign 的日志级别
        logger-level: BASIC
      # 客户端级别配置
      demo-provider:
        logger-level: FULL
  hystrix:
    enabled: true #在Feign中开启Hystrix服务降级
  #compression:
    #request:
      #enabled: false #是否对请求进行GZIP压缩
      #mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型
      #min-request-size: 2048 #超过该大小的请求会被压缩
    #response:
      #enabled: false #是否对响应进行GZIP压缩

logging:
  # 添加自定义 Feign 接口所在包的日志级别为 `DEBUG`
  level: #配置需要开启日志的Feign客户端 : 修改日志级别
    com.macro.cloud.service.UserService: debug
更多配置项

通过 FeignClientConfiguration 配置属性类,可以看到配置文件所支持的 FeignClient 的所有配置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Feign 日志级别。默认为 NONE
private Logger.Level loggerLevel;
// 请求的连接超时时长,单位:毫秒。默认为 10 * 1000 毫秒
private Integer connectTimeout;
// 请求的读取超时时长,单位:毫秒。默认为 60 * 1000 毫秒
private Integer readTimeout;
// 重试策略。默认为不重试
private Class<Retryer> retryer;
// 错误解码器
private Class<ErrorDecoder> errorDecoder;
// 请求拦截器
private List<Class<RequestInterceptor>> requestInterceptors;
// 是否对响应状态码为 404 时,进行解码。默认为 false 
private Boolean decode404;
// 解码器。
// 为空时,默认创建 SpringDecoder Bean
private Class<Decoder> decoder;
// 解码器。默认为 SpringEncoder
// 为空时,默认创建 SpringEncoder Bean
private Class<Encoder> encoder;
// 契约。
// 为空时,默认创建 SpringMvcContract Bean,提供对 SpringMVC 注解的支持
private Class<Contract> contract;
配置类

Feign提供了日志打印功能,可以通过配置来调整日志级别,开启更为详细的日志,从而了解Feign中Http请求的细节。

通过java配置来使Feign打印最详细的Http请求日志信息。

日志级别:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
1
2
3
4
5
6
7
8
@Configuration
public class FeignConfig {
    
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

创建 DefaultFeignClientConfigurationDemoProviderFeignClientConfiguration 配置类,FeignClient 配置类。

这两个配置类,并没有添加 @Configuration 注解

  1. 因为,Spring Boot 项目默认扫描 DemoConsumerApplication 所在包以及子包下的所有 Bean 们。而 @Configuration 注解也是一种 Bean,也会被扫描到。
  2. 如果添加了,导致整个项目的 Feign 客户端都使用相同的 Feign 配置,就无法到达 Feign 客户端级别的自定义配置的目的。
  3. 因此,没有添加 注解。

为了避免多个 Feign 客户端级别的配置类创建的 Bean 之间互相冲突,Spring Cloud OpenFeign 通过 FeignContext 类,为每一个 Feign 客户端创建一个 Spring 子上下文

  • 全局级别的 FeignClient 配置类是在 Spring 父上下文生效
  • 客户端级别的 FeignClient 配置类在 Spring 子上下文生效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// DefaultFeignClientConfiguration.java
/**
 * 全局 FeignClient 配置类
 */
public class DefaultFeignClientConfiguration {

    @Bean
    public Logger.Level defaultFeignClientLoggerLevel() {
        return Logger.Level.BASIC;
    }

}

// DemoProviderFeignClientConfiguration.java
/**
 * 服务 demo-provider 的 FeignClient 配置类
 */
public class DemoProviderFeignClientConfiguration {

    @Bean
    @Primary // 主 Bean
    public Logger.Level feignClientLoggerLevel() {
        return Logger.Level.FULL;
    }

}
客户端级别

通过 @FeignClient 注解的 configuration 属性,可以设置指定 FeignClient 使用的配置类,即 Feign 客户端级别的自定义配置。因此修改DemoProviderFeignClient 代码如下图所示:

DemoProviderFeignClient 修改处

全局级别

通过 @EnableFeignClients 注解的 defaultConfiguration 属性,可以设置默认 FeignClient 使用的配置类,即 Feign 全局级别的自定义配置。因此修改 DemoConsumerApplication 代码如下图所示:

DemoConsumerApplication 修改处

启动类 @EnableFeignClients

在启动类上添加@EnableFeignClients注解来声明开启 Feign 客户端的功能。

1
2
3
4
5
6
7
8
9
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignServiceApplication {

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

使用 Ribbon 进行负载均衡

自动配置了?

Feign Ribbon 为 Feign Client重写 URL 解析,同时提供了智能路由和弹性能力。

集成 Ribbon 时,需要将 url 改为 Ribbon 的客户端名称,例如说 "myAppProd"

1
2
3
4
5
6
7
8
public class Example {
    
    public static void main(String[] args) {
        MyService api = Feign.builder()
                  .client(RibbonClient.create())
                  .target(MyService.class, "https://myAppProd");
    }
}

UserService 接口

真正的直接消费者。

添加UserService接口完成对user-service服务的接口绑定。

DemoProviderFeignClient 接口

创建 DemoProviderFeignClient 接口,实现对服务 demo-provider 声明式调用。

  • @FeignClient 注解,声明 Feign 客户端。其中 name 属性,为 Feign 客户端的名字,也为 Ribbon 客户端的名字,也为注册中心的服务的名字。

  • #echo(name) 方法上,添加 SpringMVC 注解,实现对 GET /demo 接口的声明式调用。

  • 方式一 @SpringQueryMap 注解:作用相当于 Feign 的 @QueryMap 注解,将 POJO 对象转换成 QueryString

    • 默认情况下,Feign 针对 POJO 类型的参数,即使我们声明为 GET,也会自动转换成 POST 类型的请求。如果去掉 @SpringQueryMap 注解,就会报如下异常:
    • Feign 自动转换成了 POST /get_demo 请求,而服务提供者提供的 /get_demo 只支持 GET 类型,因此返回响应状态码为 405 的错误。
    1
      feign.FeignException$MethodNotAllowed: status 405 reading DemoProviderFeignClient#getDemo(DemoDTO)
    
  • @RequestLine 注解:添加在方法上,设置请求方法请求地址,按照 请求方法 请求地址 格式,例如说 GET /user/get。同时,可以通过 {param} 表达式声明占位参数,搭配 @Param 注解一起使用。

  • @Headers 注解:添加在方法上,设置请求头

    • 这里,使用在 #add(UserAddRequest request) 方法上,因为 HTTP 接口 /user/add 使用 @RequestBody + JSON 格式接收参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//调用服务提供者的 REST 接口实现客户端(消费者)的 service 接口、Client 接口
@FeignClient(name = "demo-provider")
public interface DemoProviderFeignClient {

    @GetMapping("/echo")
    String echo(@RequestParam("name") String name);
    
    // GET 方式一,@SpringQueryMap + DTO,最推荐
    @GetMapping("/get_demo")
    DemoDTO getDemo(@SpringQueryMap DemoDTO demoDTO);

    // GET 方式二,相对推荐
    // 采用 SpringMVC 提供的 @RequestParam 注解,并将所有参数平铺开。
	// 参数较少的时候,可以采用这种方式。
    @GetMapping("/get_demo") 
    DemoDTO getDemo(@RequestParam("username") String username, @RequestParam("password") String password);

    // 方式三,采用 SpringMVC 提供的 @RequestParam 注解,并使用 Map 对象。
    // 非常不推荐,因为可读性差,都不知道传递什么参数。
    @GetMapping("/get_demo")
    DemoDTO getDemo(@RequestParam Map<String, Object> params);

    
    // POST 场景,唯一方式
    // 采用 SpringMVC 提供的 @RequestBody 注解,并使用 DemoDTO 对象。
    @PostMapping("/post_demo")
    DemoDTO postDemo(@RequestBody DemoDTO demoDTO);
}
添加 @FeignClient
  • 通过@FeignClient注解实现一个Feign客户端,其中的 value 表示这是对user-service服务的接口调用客户端。

可以回想下user-service中的UserController,只需将其改为接口,保留原来的SpringMvc注释即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//设置 fallback 服务降级处理类为 UserFallbackService.class
@FeignClient(value = "user-service", fallback = UserFallbackService.class)
public interface UserService {    
    
    @PostMapping("/user/create")
    CommonResult create(@RequestBody User user);

    @GetMapping("/user/{id}")
    CommonResult<User> getUser(@PathVariable Long id);

    @GetMapping("/user/getByUsername")
    CommonResult<User> getByUsername(@RequestParam String username);

    @PostMapping("/user/update")
    CommonResult update(@RequestBody User user);

    @PostMapping("/user/delete/{id}")
    CommonResult delete(@PathVariable Long id);
}
UserFallbackService

服务降级使用起来非常方便,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可。

需要注意的是它实现了UserService接口,并且对接口中的每个实现方法进行了服务降级逻辑的实现。

下面为UserService接口、添加一个服务降级实现类 UserFallbackService。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class UserFallbackService implements UserService {
    @Override
    public CommonResult create(User user) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult<User> getUser(Long id) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult<User> getByUsername(String username) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult update(User user) {
        return new CommonResult("调用失败,服务被降级",500);
    }

    @Override
    public CommonResult delete(Long id) {
        return new CommonResult("调用失败,服务被降级",500);
    }
}

UserFeignController

创建 ConsumerController 类,提供一个通过 Feign 调用服务提供者的 HTTP 接口。

添加UserFeignController调用UserService实现服务调用。

直接访问服务消费者的 http://127.0.0.1:28080/hello02?name=yudaoyuanma 接口,返回结果为 consumer:16445-provider:yudaoyuanma。能够调通 HTTP 接口,说明使用继承特性成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RestController
@RequestMapping("/user")
public class UserFeignController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public CommonResult getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @GetMapping("/getByUsername")
    public CommonResult getByUsername(@RequestParam String username) {
        return userService.getByUsername(username);
    }

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return userService.create(user);
    }

    @PostMapping("/update")
    public CommonResult update(@RequestBody User user) {
        return userService.update(user);
    }

    @PostMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
        return userService.delete(id);
    }
}

Feign 单独使用

在使用 Spring Cloud 的项目中,大多数是通过 Feign 调用从 Ribbon 负载均衡选择的服务实例,而 Ribbon 是通过注册中心获取到的服务实例列表。但是有些场景下,可能想要单独使用 Feign 调用,例如说:

  • 调用第三方服务,例如说短信云服务、推送云服务。
  • 调用的虽然是内部服务,但是并没有注册到注册中心,而是使用 Nginx 代理并负载均衡实现高可用。
DemoProviderFeignClient

@FeignClient 注解的 url 属性设置要调用的服务的地址

  • 不过要注意,保持 name 属性和 url 属性的 host 是一致的,不然还是会使用 Ribbon 进行负载均衡。

比如,修改 DemoProviderFeignClient 接口,改成调用 http://www.iocoder.cn

1
2
3
4
5
6
7
8
9
10
11
//@FeignClient(name = "demo-provider")
@FeignClient(name = "iocoder", url = "http://www.iocoder.cn") // 注意,保持 name 属性和 url 属性的 host 是一致的。
public interface DemoProviderFeignClient {

//    @GetMapping("/echo")
//    String echo(@RequestParam("name") String name);

    @GetMapping("/") // 请求首页
    String echo(@RequestParam("name") String name);

}

Hystrix

Feign Hystrix 使用 Hystrix 提供了断路器的支持。

若要使用 Hystrix 时,需要将 Hystrix 模块加入 classpath 中。然后,使用 HystrixFeign.builder

1
2
3
4
5
6
public class Example {

    public static void main(String[] args) {
        MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
    }
}

功能演示

负载均衡功能

  • 启动eureka-service,两个user-service,feign-service服务,启动后注册中心显示如下:

1
2
3
2019-10-04 15:15:34.829  INFO 9236 --- [nio-8201-exec-5] c.macro.cloud.controller.UserController  : 根据id获取用户信息,用户名称为:macro
2019-10-04 15:15:35.492  INFO 9236 --- [io-8201-exec-10] c.macro.cloud.controller.UserController  : 根据id获取用户信息,用户名称为:macro
2019-10-04 15:15:35.825  INFO 9236 --- [nio-8201-exec-9] c.macro.cloud.controller.UserController  : 根据id获取用户信息,用户名称为:macro

服务降级功能

  • 关闭两个user-service服务,重新启动feign-service;

  • 调用http://localhost:8701/user/1进行测试,可以发现返回了服务降级信息。

日志打印功能

调用http://localhost:8701/user/1进行测试,可以看到以下日志。

  • 从日志中也可以看出 Feign 是调用日志组件的 DEBUG 级别打印日志。
1
2
3
4
5
6
7
8
9
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] ---> GET http://user-service/user/1 HTTP/1.1
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] ---> END HTTP (0-byte body)
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] <--- HTTP/1.1 200 (9ms)
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] content-type: application/json;charset=UTF-8
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] date: Fri, 04 Oct 2019 07:44:03 GMT
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] transfer-encoding: chunked
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] 
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] {"data":{"id":1,"username":"macro","password":"123456"},"message":"操作成功","code":200}
DEBUG 5204 --- [-user-service-2] com.macro.cloud.service.UserService      : [UserService#getUser] <--- END HTTP (92-byte body)

HTTP 客户端

默认情况下,Feign 通过 JDK 自带的 HttpURLConnection 封装了 Client.Default,实现 HTTP 调用的客户端。因为 HttpURLConnection 缺少对 HTTP 连接池的支持,所以性能较低,在并发到达一定量级后基本会出现。

因此 Feign 提供了另外两个 HTTP 客户端:

请求重试

Feign 和 Ribbon 都有请求重试的功能,两者都启用该功能的话,会产生冲突的问题。因此,有且只能启动一个的重试。

  • 目前比较推荐的是使用 Ribbon 来提供重试。

  • 在 Spring Cloud OpenFeign 中,默认创建的是 NEVER_RETRY 不进行重试。如此,只需要配置 Ribbon 的重试功能即可。

例如,搭建下 Feign + Ribbon 请求重试的使用示例。

0%