OpenFeign 入门教程 - 新版篇

大纲

新版本使用

版本说明

组件名版本
Spring Boot3.2.0
Spring Cloud2023.0.0
spring-cloud-starter-openfeign4.1.0

新版本支持的特性

新版本特性概览

  • 支持 Sentinel 和它的 Fallback
  • 支持 Spring Cloud LoadBalancer 的负载均衡客户端

默认集成了 LoadBalancer

由于 NetFlix Ribbon 的停更,Spring Cloud 官方推荐使用 Spring Cloud LoadBalancer 来替代 Spring Cloud Ribbon,详细说明请看 这里。从 Spring Cloud 2020.0 版本(即 Spring Cloud Alibaba 2.2.5 版本)开始,OpenFeign 就默认集成了 Spring Cloud LoadBalancer 作为客户端负载均衡组件,而且它是可选的依赖组件。因此,在使用 Spring Cloud OpenFeign 时,不需要额外引入 spring-cloud-starter-loadbalancer 依赖,客户端负载均衡功能就会默认启用。不过,如果希望自定义负载均衡策略或调整 Spring Cloud LoadBalancer 的配置,则可能需要明确引入 spring-cloud-starter-loadbalancer 依赖以便进行更细粒度的控制。

设置请求超时时间

在 OpenFeign 中,可以通过配置 connectTimeoutreadTimeout 参数来指定请求超时时间,其中 connectTimeout 是连接超时时间,而 readTimeout 是请求处理超时时间。值得一提的是,新版本 OpenFeign 默认的请求处理超时时间是 60 秒,当服务端处理超过规定时间后,会导致 Feign 客户端抛出异常。

官方文档说明

OpenFeign 官方文档 中,配置参数说明如下:

官方文档说明

OpenFeign 官方文档 中,部分可配置参数列表如下:

全局设置生效

通过 default 在全局配置 Feign 的请求超时时间,所有的 FeignClient 都会应用生效。

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
openfeign:
client:
config:
# 全局配置
default:
# 连接超时时间(单位为毫秒)
connectTimeout: 2000
# 请求处理超时时间(单位为毫秒)
readTimeout: 2000

局部设置生效

通过服务名称为特定的服务单独配置请求超时时间,单独配置的超时时间将会覆盖全局配置。

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
openfeign:
client:
config:
# 为特定的服务单独配置
provider-service:
# 连接超时时间(单位为毫秒)
connectTimeout: 3000
# 请求处理超时时间(单位为毫秒)
readTimeout: 3000

案例代码下载

代码下载

完整的案例代码可以从这里下载。

设置失败重试次数

在默认情况下,OpenFeign 会创建 Retryer.NEVERRETRY 类型为 Retryer 的 Bean,也就是说默认会禁用重试机制。请注意,这种重试行为与 Feign 的默认行为不同,它会自动重试 IOExceptions,将它们视为与网络相关的瞬态异常,以及从 ErrorDecoder 抛出的任何 RetryableException

全局设置生效

若希望在全局设置 Feign 的失败重试次数,可以自定义一个 Retryer 的 Bean,并加载到 Spring IOC 容器中,如下所示:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class FeignConfig {

@Bean
public Retryer retryer() {
// 初始间隔时间为100ms,重试最大间隔时间为1000ms,最大请求次数为3(1次初始调用,2次重试调用)
return new Retryer.Default(100, 1000, 3);
}

}

又或者在 application.yml 中,直接指定全局使用哪个失败重试类(Retryer)

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
openfeign:
client:
config:
# 全局配置
default:
# 连接超时时间(单位为毫秒)
connectTimeout: 2000
# 请求处理超时时间(单位为毫秒)
readTimeout: 2000
# 设置失败重试类
retryer: feign.Retryer.Default

提示

使用 YML 配置文件的方式会调用 Retryer.Default 类的默认构造方法,效果相当于调用 new Retryer.Default(100, 1000, 5);

Retryer.Default(long period, long maxPeriod, int maxAttempts) 构造方法的参数说明如下:

  • period:第一次重试之前的间隔时间,单位为毫秒
  • maxPeriod: 最大间隔时间,单位为毫秒
  • maxAttempts:最大请求次数

特别注意

在消费方(接口调用方)设置失败重试次数之后,IDE 控制台并不会看到多次重试过程,只会显示正确的返回结果,这是 OpenFeign 的日志打印问题导致的。如果希望重试效果明显一点,可以配置 OpenFeign 日志功能,将详细的重试日志打印出来;又或者自行打印接口开始调用到接口结束调用的时间差,以此观察失败重试机制是否生效。

局部设置生效

通过服务名称为特定的服务单独配置失败重试类(Retryer),此方式会覆盖全局配置的 Retryer。

  • 首先定义一个失败重试配置类,特别注意,该配置类没有被 @Configuration 注解标注,不然该配置类默认会在全局生效
1
2
3
4
5
6
7
8
9
public class FeignRetryerConfig {

@Bean
public Retryer feignRetryer() {
// 初始间隔时间为100ms,重试最大间隔时间为1000ms,最大请求次数为3(1次初始调用,2次重试调用)
return new Retryer.Default(100, 1000, 3);
}

}
  • 然后通过 @FeignClient 注解的 valueconfiguration 属性来指定服务名称和失败重试配置类,这样该服务只会使用指定的失败重试类(Retryer)
1
2
3
4
5
6
7
@FeignClient(value = "provider-service", configuration = FeignRetryerConfig.class)
public interface ProviderFeignApi {

@GetMapping("/provider/add")
String add(@RequestParam Integer a, @RequestParam Integer b);

}

又或者在 application.yml 中,直接为特定的服务指定使用 Retryer.Default 失败重试类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
openfeign:
client:
config:
# 为特定的服务单独配置
provider-service:
# 连接超时时间(单位为毫秒)
connectTimeout: 3000
# 请求处理超时时间(单位为毫秒)
readTimeout: 3000
# 设置失败重试类
retryer: feign.Retryer.Default

提示

使用 YML 配置文件的方式会调用 Retryer.Default 类的默认构造方法,效果相当于调用 new Retryer.Default(100, 1000, 5);

案例代码下载

代码下载

完整的案例代码可以从这里下载。

自定义失败重试机制

常见的失败重试机制有以下三种:

  • 固定间隔重试:每次重试之间的时间间隔固定不变,例如每次重试之间相隔 1 秒。
  • 指数重试:每次重试之间的时间间隔按指数递增。例如,初始时间间隔为 1 秒,每次重试后加倍,即第一次 1 秒,第二次 2 秒,第三次 4 秒,以此类推。
  • 随机间隔重试:每次重试之间的时间间隔是随机的,通过引入随机性来防止多个失败请求同时发生。例如,每次重试的时间间隔在一定范围内随机选择。

自定义失败重试类

为了自定义失败重试机制,可以实现 Retryer 接口,并重写 continueOrPropagate() 方法。值得一提的是,continueOrPropagate() 方法是 Retryer 接口中最重要的方法,它接受一个 RetryableException 作为参数,且没有返回值。当执行这个方法时,它要么抛出一个异常,要么成功执行完(通常是在休眠一段时间后)。如果它没有抛出异常,Feign 将继续重试调用。如果抛出异常,该异常将被传播,从而有效地以错误形式结束方法调用。

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
42
43
44
45
46
47
48
49
50
51
52
53
import feign.RetryableException;
import feign.Retryer;
import java.util.concurrent.TimeUnit;

public class CustomRetryer implements Retryer {

/**
* 重试间隔时间(单位毫秒)
*/
private final long backoff;

/**
* 最大重试次数
*/
private final int maxAttempts;

/**
* 当前重试次数
*/
private int attempt;

public CustomRetryer() {
this.backoff = 100L;
this.maxAttempts = 3;
this.attempt = 1;
}

public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}

@Override
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}

try {
TimeUnit.MICROSECONDS.sleep(this.backoff); // 固定间隔重试
// TimeUnit.MICROSECONDS.sleep(this.backoff * attempt); // 间隔时间线性递增
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}

@Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}

}

全局设置生效

为了让自定义失败重试类(Retryer)全局设置生效,可以将该 Retryer 的 Bean 加载到 Spring IOC 容器中,如下所示:

1
2
3
4
5
6
7
8
9
@Configuration
public class FeignConfig {

@Bean
public Retryer retryer() {
return new CustomRetryer(100, 3);
}

}

又或者在 application.yml 中,直接指定全局使用哪个自定义失败重试类(Retryer)

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
openfeign:
client:
config:
# 全局配置
default:
# 连接超时时间(单位为毫秒)
connectTimeout: 2000
# 请求处理超时时间(单位为毫秒)
readTimeout: 2000
# 设置自定义失败重试类
retryer: com.clay.retry.CustomRetryer

提示

使用 YML 配置文件的方式会调用 CustomRetryer 类的默认构造方法,效果相当于调用 new CustomRetryer(100, 3);

局部设置生效

通过服务名称为特定的服务单独配置自定义失败重试类(Retryer),此方式会覆盖全局配置的 Retryer。

  • 首先定义一个失败重试配置类,特别注意,该配置类没有被 @Configuration 注解标注,不然该配置类默认会在全局生效
1
2
3
4
5
6
7
8
public class FeignRetryerConfig {

@Bean
public Retryer feignRetryer() {
return new CustomRetryer(100, 3);
}

}
  • 然后通过 @FeignClient 注解的 valueconfiguration 属性来指定服务名称和失败重试配置类,这样该服务只会使用指定的自定义失败重试类(Retryer)
1
2
3
4
5
6
7
@FeignClient(value = "provider-service", configuration = FeignRetryerConfig.class)
public interface ProviderFeignApi {

@GetMapping("/provider/add")
String add(@RequestParam Integer a, @RequestParam Integer b);

}

又或者在 application.yml 中,直接为特定的服务指定使用哪个自定义失败重试类(Retryer)

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
openfeign:
client:
config:
# 为特定的服务单独配置
provider-service:
# 连接超时时间(单位为毫秒)
connectTimeout: 3000
# 请求处理超时时间(单位为毫秒)
readTimeout: 3000
# 设置自定义失败重试类
retryer: com.clay.retry.CustomRetryer

提示

使用 YML 配置文件的方式会调用 CustomRetryer 类的默认构造方法,效果相当于调用 new CustomRetryer(100, 3);

案例代码下载

代码下载

完整的案例代码可以从这里下载。

Spring Retry 使用

除了可以使用 OpenFeign 自身提供的失败重试机制,还可以使用 Spring Retry 组件来实现,它提供了 @Retryable@Backoff 等注解。值得一提的是,Spring Retry 的底层是基于 AOP 来实现的,这里不再累述。

替换默认 Http 客户端

OpenFeign 中的 HttpClient 如果不做特殊配置,底层默认会使用 JDK 原生的 HttpURLConnection 发送 HTTP 请求。由于默认的 HttpURLConnection 没有连接池,性能和效率比较低,如果采用默认的,那么对性能会造成较大影响。值得一提的是,OpenFeign 支持使用 Apache HttpClient 和 Okhttp 来替换 JDK 原生的 HttpURLConnection。

特别注意

从 Spring Cloud OpenFeign 4 开始,官方不再支持 Feign Apache HttpClient 4,建议改用 Apache HttpClient 5,官方文档 说明如下图所示:

Apache HttpClient 替换

替换步骤
  • 引入以下依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- feign hc 5 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>

<!-- apache httpclient 5 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
  • 启用 Apache HttpClient 5
1
2
3
4
5
6
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true

Apache HttpClient 5 的自动配置类

在 OpenFeign 中,Apache HttpClient 的自动配置类有两个,分别是 HttpClient5FeignConfigurationHttpClient5FeignLoadBalancerConfiguration

验证替换

HttpClient5FeignLoadBalancerConfiguration.feignClient() 方法内打上断点(约 63 行),重新启动项目(切记不要以单元测试的方式启动,否则可能会因缺少配置导致无法进入打断点的代码),此时可以看到确实进行了 ApacheHttpClient 的声明;再将 spring.cloud.openfeign.httpclient.hc5.enabled 设置为 false 后,断点就进不来了,由此可以验证 ApacheHttpClient 替换成功。另外,从异常堆栈信息也可以看出 ApacheHttpClient 是否替换成功,如下图所示:

案例代码下载

代码下载

完整的案例代码可以从这里下载。

Okhttp 替换

替换步骤
  • 引入以下依赖
1
2
3
4
5
6
<!-- Okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>13.1</version>
</dependency>
  • 启用 Okhttp
1
2
3
4
5
spring:
cloud:
openfeign:
okhttp:
enabled: true

Okhttp 的自动配置类

在 OpenFeign 中,Okhttp 的自动配置类有两个,分别是 OkHttpFeignConfigurationOkHttpFeignLoadBalancerConfiguration,都默认带有连接池功能。

自定义拦截器

为了方便接口调试,对请求和响应进行日志打印,可以增加以下 Okhttp 拦截器配置。

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
@Slf4j
@Configuration
public class FeignOkHttpConfig {

@Bean
public okhttp3.OkHttpClient.Builder okHttpClientBuilder() {
return new okhttp3.OkHttpClient.Builder().addInterceptor(new LoggingInterceptor());
}

/**
* Okhttp 请求日志拦截器
*/
private static class LoggingInterceptor implements Interceptor {

@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long start = System.nanoTime();
log.info(
String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
okhttp3.Response response = chain.proceed(request);
long end = System.nanoTime();
log.info(
String.format("Received response for %s in %.1fms%n%s", response.request().url(), (end - start) / 1e6d,
response.headers()));
return response;
}

}

}
验证替换

OkHttpFeignLoadBalancerConfiguration.feignClient() 方法内打上断点(约 58 行),然后正常启动项目(切记不要以单元测试的方式启动,否则可能会因缺少配置导致无法进入打断点的代码),此时可以看到确实进行了 OkHttpClient 的声明;再将 spring.cloud.openfeign.okhttp.enabled 设置为 false 后,断点就进不来了,由此可以验证 OkHttpClient 替换成功。另外,从异常堆栈信息也可以看出 Okhttp 是否替换成功,如下图所示:

案例代码下载

代码下载

完整的案例代码可以从这里下载。

开启请求 / 响应 GZIP 压缩

参数说明

Spring Cloud OpenFeign 支持对 HTTP 请求和响应进行 GZIP 压缩,以减少通信过程中的性能损耗。官方文档的说明如下图所示:

通过下面的两个参数设置,就能开启请求与响应的压缩功能:

1
2
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true

还可以对请求压缩做一些更细致的配置,比如下面的配置内容指定了压缩的请求数据类型,并设置了请求压缩的大小下限,只有超过这个大小的请求才会进行压缩:

1
2
3
4
5
6
# 启用请求压缩
spring.cloud.openfeign.compression.request.enabled=true
# 触发压缩数据类型
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
# 最小触发压缩的大小
spring.cloud.openfeign.compression.request.min-request-size=2048

配置示例

完整的配置示例如下:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
openfeign:
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true

开启日志打印功能

OpenFeign 提供了日志打印功能,开发者可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。简而言之,就是对 Feign 接口的调用情况进行监控和输出。

日志级别

OpenFeign 支持的日志级别如下:

  • NONE:默认的日志级别,不打印任何日志。
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间。
  • HEADERS:除了 BASIC 中定义的日志信息之外,还有请求和响应的头信息。
  • FULL:除了 HEADERS 中定义的日志信息之外,还有请求和响应的正文及元数据。

使用步骤

定义日志级别 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}

}

在 YML 配置文件中,配置 Feign 客户端的日志输出。特别注意,Feign 日志记录仅响应 DEBUG 日志级别的配置。

1
2
3
4
logging:
level:
# OpenFeign日志以什么级别打印哪个接口
com.clay.cloud.api.PayFeignApi: debug

如果开启了请求 / 响应 GZIP 压缩,那么控制台输出的日志信息如下:

1
2
3
4
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> GET http://cloud-payment-service/pay/get HTTP/1.1
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: gzip
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: deflate
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> END HTTP (0-byte body)

如果配置了失败重试次数,那么控制台输出的日志信息如下:

1
2
3
4
5
6
7
8
9
10
11
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> RETRYING
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> GET http://cloud-payment-service/pay/get HTTP/1.1
036 [http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: gzip
036 [http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: deflate
036 [http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> END HTTP (0-byte body)

[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> RETRYING
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> GET http://cloud-payment-service/pay/get HTTP/1.1
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: gzip
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] Accept-Encoding: deflate
[http-nio-8021-exec-1] DEBUG com.turing.cloud.api.PayFeignApi - [PayFeignApi#getAppInfo] ---> END HTTP (0-byte body)