超时时间设置

Ribbon配置

由于Spring Cloud Feign的客户端负载均衡时通过Spring Cloud Ribbon实现的,因此我们可以直接通过配置Ribbon客户端的方式来自定义各个服务客户端调用的参数。接下来就学习如何在使用Spring Cloud Feign的工程中使用Ribbon的配置。当然后续也会学习如何直接设置Feign的超时时间。

全局配置

全局配置的方式非常简单,只需在application.yml配置文件中使用诸如ribbon.<key>=<value>的方式来设置Ribbon的各项默认参数。

请注意此时必须选择Feign包内自带的Ribbon,因为Maven自带的Ribbon可能会冲突失效。如笔者这里使用的SpringBoot版本是2.3.3,而SpringCloud版本则是Hoxton.SR7,因此对应的Ribbon可能会与Feign自带的版本产生冲突:

接下来举一个例子来演示ribbon.<key>=<value>的方式,如修改默认的客户端调用超时时间如下所示:

1
2
3
4
# 全局配置
ribbon:
ConnectTimeout: 500 # 单位毫秒
ReadTimeou
指定服务配置

其实在大多数情况下,我们对于服务调用的超时时间可能会有所调整,因此仅仅依赖默认的全局配置是无法满足日常的需求。

在使用Spring Cloud Feign的时候,针对各个服务客户端进行个性化配置的方式与使用Spring Cloud Ribbon时的配置方式是一样的,都采用了<client>.ribbon.key=value的格式进行设置。那么问题来了,这里的<client>所指代的Ribbon客户端在哪里呢?

仔细回忆一下我们在定义Feign客户端的时候,使用了@FeignClient注解。在初始化过程中,Spring Cloud Feign会根据该注解的name属性或value属性指定的服务名,自动创建一个同名的Ribbon客户端。也就是说在之前的例子中,我们使用@FeignClient(value="hello-service")注解来创建Feign客户端的时候,同时也创建了一个名为hello-service的Ribbon客户端。既然都知道了客户端的名字,那么就可以使用@FeignClient注解中的name或者value属性值来设置对应的Ribbon参数。

以前面设置的hello-service为例,在application.yml配置文件中添加如下配置信息:

1
2
3
4
5
6
7
8
# 指定服务配置
hello-service:
ribbon:
ConnectTimeout: 500
ReadTimeout: 2000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 2
MaxAutoRetries: 1

可以看到这里的hello-service就是服务的名称。

Feign配置

接下来将学习如何直接设置Feign的超时时间,这里也分两种配置:全局配置和指定服务配置,分别介绍如下。

全局配置

请注意这里笔者以Feign的默认客户端(Client.Default)为例进行说明,开发者只需在application.yml配置文件中添加如下配置信息:

1
2
3
4
5
6
7
8
9
10
# 全局配置
feign:
client:
config:
default:
ConnectTimeout: 500 # 单位毫秒
ReadTimeout: 2000 # 单位毫秒
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 2
MaxAutoRetries: 1
指定服务配置

对于指定服务配置,笔者以之前的hello-service服务为例进行说明,也就是说该配置仅对服务名为hello-service的服务生效。开发者只需在application.yml配置文件中添加如下配置信息:

1
2
3
4
5
6
7
8
9
10
# 指定服务配置
feign:
client:
config:
hello-service:
ConnectTimeout: 500 # 设置连接超时时间(单位毫秒)
ReadTimeout: 2000 # 设置读取超时时间(单位毫秒)
OkToRetryOnAllOperations: true # 对所有操作请求都进行重试
MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
超时时间设置小结

(1)如果同时配置了Ribbon和Feign,那么优先生效Feign的配置;
(2)如果想让Ribbon的配置生效,就必须通过服务注册中心来进行微服务间的相互调用。需要说明的是如果开发者在本地通过@FeignClient注解的url参数来进行服务间的调用,那么此时通过Ribbon设置的超时时间等参数将会失效,而通过Feign设置的超时时间等参数不会受影响,可以正常使用。综上所述,笔者建议优先使用Feign来设置超时时间,因为既然是封装了Ribbon,那就没必要再通过底层来实现相应的功能,否则会造成封装失败的问题。

重试机制

Spring Cloud Feign默认实现了请求的重试机制,而前面我们对于hello-service服务客户端的配置内容就是对于请求超时以及重试机制配置的详情,这一点在前面已经进行了介绍,这里就不赘述了,但是可以通过修改之前的例子来做一些验证。

这是之前在erueka-client(服务名称为hello-service)项目中/hello接口的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping(value = "/hello")
public String index() throws Exception {
List<ServiceInstance> instances = client.getInstances("hello-service");
//让处理线程等待几秒钟
int sleepTime = new Random().nextInt(3000);
logger.info("sleepTime is: "+sleepTime);
Thread.sleep(sleepTime);

for(int i=0;i<instances.size();i++){
logger.info("/hello,host: "+instances.get(i).getHost()+
",service_id: "+instances.get(i).getServiceId());
}
return "hello,world!";
}

这里我们通过一个随机函数来模拟程序的执行时间,然后测试超时重试机制是否生效。注意前提是在feign-consumer应用中增加前面所提到的重试配置参数,即下面的几行代码:

1
2
3
4
5
6
7
8
# 指定服务配置
feign:
client:
config:
hello-service:
OkToRetryOnAllOperations: true # 对所有操作请求都进行重试
MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数

在上面我们设置hello-service.ribbon.MaxAutoRetries参数为1,所以重试策略会先尝试访问首选实例一次,如果失败才会更换实例访问,而更换实例访问次数则通过hello-service.ribbon.MaxAutoRetriesNextServer参数来设置,此处设置值为2,所以会尝试更换两次实例进行重试。

接着开发者可以按照前面的顺序来启动这些应用,然后在浏览器地址栏中多次访问http://localhost:3001/hello链接,可以看到由于随机函数的存在,使得请求可能会发生超时,且当请求发生超时的时候,开发者可以在hello-service服务的控制台中看到类似的输出内容,如下所示:

从上面的输出信息中可以知道,访问的第一次请求延迟时间为69毫秒,服务正常请求结果,而第二次请求延迟时间为2737毫秒,由于前面设置的超时时间为2000毫秒,于是Feign客户端发起了重试,Feign客户端在进行服务调用时,虽然经历了一次失败,但是通过重试机制最终还是获得了结果。所以重试机制对于构建高可用的服务集群显得尤为重要,而Spring Cloud Feign对此也提供了足够的支持。

请注意Ribbon的超时和Hystrix的超时是两个不同的概念,为了让上述实现有效,我们需要让Hystrix的超时时间大于Ribbon的超时时间,否则Hystrix命令超时后,该命令直接熔断,重试机制没有存在的必要。

Hystrix配置

在Spring Cloud Feign中,除了前面所说的用于实现客户端负载均衡的Ribbon组件外,还有用于服务保护与容错的工具Hystrix。在默认情况下,Spring Cloud Feign会将所有Feign客户端的方法都封装到Hystrix命令中进行服务保护。且在上面也提到了Hystrix的超时时间,那么如何在使用Spring Cloud Feign时配置Hystrix属性及如何实现服务降级呢?往下看你就知道。

全局配置

其实对于Hystrix的全局配置,它和Spring Cloud Ribbon的全局配置类似,可以直接使用它的默认配置前缀hystrix.command.default,比如设置全局的超时时间:

1
2
3
4
5
6
7
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000

另外请注意在对Hystrix进行配置之前,需要确认feign.hystrix.enabled参数没有设置为false(默认设置为true),否则该参数设置会关闭Feign客户端的Hystrix支持。如果开发者在上面测试重试机制时,除了使用上述的配置来增加熔断超时时间外,还可以通过feign.hystrix.enabled=false来关闭Hystrix功能,或者使用hystrix.command.default.execution.timeout.enabled=false来关闭熔断功能。

禁用Hystrix

前面介绍了在Spring Cloud Feign中,开发者可以通过使用feign.hystrix.enabled=false设置来关闭Hystrix功能。另外如果不想全局关闭Hystrix支持,只是针对某个服务客户端关闭Hystrix支持时,只需要通过使用@Scope("prototype")注解为指定的客户端配置Feign.Builder实例。这里以禁用hello-service服务客户端的Hystrix支持为例进行介绍,相应的操作步骤如下所示:

(1)打开feign-consumer项目,在里面新建一个config包,然后在该包被新建一个用于关闭Hystrix支持的配置类DisableHystrixConfiguration,相应的代码为:

1
2
3
4
5
6
7
8
@Configuration
public class DisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}

(2)打开feign-consumer项目,在RefactorHelloService接口的@FeignClient(value = "hello-service")注解上通过configuration参数引入第一步中配置的类字节码信息:

1
2
3
@FeignClient(value = "hello-service",configuration = DisableHystrixConfiguration.class)
public interface RefactorHelloService extends HelloService {
}

通过以上两步我们就完成了对hello-service服务客户端的Hystrix禁止支持的配置。

指定命令配置

对于Hystrix命令的配置,在实际开发过程中会根据实际业务场景制定出不同的配置方案。其配置方法和传统的Hystrix命令的参数配置非常相似,一般采用hystrix.command.<commandKey>作为前缀,而<commandKey>默认情况下会采用Feign客户端中的方法名作为标识,所以针对前面介绍重试机制时对于/hello接口的熔断超时时间的配置其实是可以通过其方法名作为<commandKey>来进行配置的,如下所示:

1
2
3
4
5
6
7
8
# 默认配置
hystrix:
command:
hello:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000

请注意在使用指定命令配置的时候,方法名可能有重复,此时相同方法名的Hystrix配置会共用,所以在进行方法定义和配置的时候需要格外谨慎,避免出现这种共用情况的发生。当然也可以通过重写Feign.Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder来实现。

服务降级配置

Hystrix提供的服务降级是服务容错的重要功能,由于Spring Cloud Feign在定义服务客户端的时候与Spring Cloud Ribbon有较大区别,因为HystrixCommand被封装起来了,无法像之前在介绍Spring Cloud Hystrix时,开发者可以通过HystrixCommand注解的fallback参数来指定具体的服务降级处理方法。

但是呢,Spring Cloud Feign对上述操作进行了简化,提供了另外一种简单的定义方式。接下来我们在之前创建的feign-consumer项目中进行改造,使用这种简单的定义方式。

其实非常简单,在Spring Cloud Feign中,服务降级逻辑的实现只需要为Feign客户端的定义接口编写一个具体的接口实现类,然后每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑。举个例子,回到eureka-client项目(服务名为hello-service)中,在其service包内新建一个fallback包,并在该fallback包内新建HelloServiceFallback类,注意该类需要实现HelloService接口,且该实现类中每个重写方法的实现逻辑都可以用来定义相应服务的降级逻辑,相应的代码如下所示:

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
@Component
public class HelloServiceFallback implements HelloService {
@Override
public String world() {
return "error";
}

@Override
public String feignOne(@RequestParam("name") String name) {
return "error";
}

@Override
public Movie feignTwo(@RequestHeader("name") String name,
@RequestHeader("author") String author,
@RequestHeader("price")Double price) {
return new Movie("error","error",999.00);
}

@Override
public String feignThree(@RequestBody Movie movie) {
return "error";
}

@Override
public String index() {
return "error";
}
}

接着回到服务绑定接口HelloService中,通过使用@FeignClient注解的fallback属性来指定其对应的服务降级的实现类,即前面所定义的HelloServiceFallback,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FeignClient(name = "hello-service",fallback = HelloServiceFallback.class)
public interface HelloService {
@GetMapping(value = "/world")
String world();

@GetMapping(value = "/feignOne")
String feignOne(@RequestParam("name")String name);

@GetMapping(value = "/feignTwo")
Movie feignTwo(@RequestHeader("name")String name,
@RequestHeader("author")String author,
@RequestHeader("price")Double price);

@PostMapping(value = "/feignThree")
String feignThree(@RequestBody Movie movie);

@GetMapping(value = "/hello")
String index();
}

注意如果之前开发者配置了禁用hello-service服务的Hystrix客户端支持,那么此时需要将禁用逻辑给去掉。还有由于笔者使用Spring Cloud的版本为Hoxton.SR7,而它默认是禁用了Feign对Hystrix的支持,如下所示:

但是为了演示服务降级,必须开启Feign对Hystrix的支持,开发者可以在application.yml配置文件中新增如下代码:

1
2
3
4
# 开启Hystrix服务熔断功能
feign:
hystrix:
enabled: true

最后启动服务注册中心eureka-server和feign-consumer项目,但是不启动hello-service服务,然后在浏览器地址栏中访问http://localhost:3001/feign-consumer2链接,很明显该接口会调用HelloService接口中5个绑定接口,然而由于hello-service服务未启动,会触发服务降级机制,然后开发者在页面会显示如下信息:

从页面返回的内容可以知道,以上信息都是我们在HelloServiceFallback类中实现的内容,每个服务接口的断路器实际上就是该接口实现类中对于方法的重写实现逻辑。

其他配置

请求压缩

Spring Cloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗。开发者只需在application.yml配置文件中添加以下几行代码就能开启请求与响应的压缩功能:

1
2
3
4
5
6
7
# 开启请求与响应的压缩
feign:
compression:
request:
enabled: true # 默认为false
response:
enabled: true # 默认为false

不仅如此,Feign还支持对请求压缩做更为细致的设置,可以设置压缩的请求数据类型(针对某种请求类型进行压缩,如文件类型)、压缩大小的上下限(超过该设置大小才进行压缩),如下所示:

1
2
3
4
5
6
7
8
9
# 开启请求与响应的压缩
feign:
compression:
request:
enabled: true # 默认为false
mime-types: text/xml, application/xml, application/json # 默认值
min-request-size: 2048 # 默认值
response:
enabled: true # 默认为false

上面笔者设置的值都是采用了Feign提供的默认值,开发者可以根据实际情况对其进行修改。

日志配置

Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign.Logger实例,我们可以利用该日志对象的DEBUG模式来帮助分析Feign的请求细节。那么如何对日志进行设置呢?很简单,开发者只需在application.yml配置文件内使用logging.level.<FeignClient>的参数配置格式来开启指定Feign客户端的DEBUG日志,其中<FeignClient>是Feign客户端定义接口的完整路径。同时还需要修改默认的日志级别NONE为其他级别,以便于输出相应的信息。

举个例子来说,在前面我们定义了HelloService接口,那么如果开发者想查看该类在运行过程中的日志信息可以按照如下步骤进行配置:

第一步,在配置文件中配置需要进行日志输出的类或者接口的路径信息,格式为logging.level.+Feign客户端路径,如下所示:

1
2
3
4
5
6
7
8
# 开启日志,格式为logging.level.+Feign客户端路径
logging:
level:
com:
envy:
feignconsumer:
service:
HelloService: debug

第二步,修改默认的日志级别。由于Feign客户端默认的Logger.Level对象定义级别为NONE,该级别不会记录任何Feign调用过程中的信息,因此如果想记录信息必须调整该日志级别。

这里调整日志级别分为两种,一种是针对全局的日志级别配置,还有一种是针对不同的Feign客户端的日志级别配置,这里分别也进行介绍。

对于全局的日志级别配置,开发者可以在项目的入口类中添加一个用于创建Logger.LevelBean对象的方法。举个例子,如在feign-consumer项目的入口类FeignConsumerApplication中添加一个Bean方法用于返回一个Logger.LevelBean对象,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumerApplication {

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

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

对于不同的Feign客户端的日志级别配置,开发者可以通过定义多个配置类,然后针对每个具体的Feign客户端指定其配置类,以实现不同日志级别的设置。请注意在配置类中
开发者需要添加一个用于创建Logger.LevelBean对象的方法。然后在@FeignClient注解上,通过configuration属性来指定配置类信息。

举个例子,如在feign-consumer项目的config包内新建一个FullLogConfiguration配置类,用于设置FULL级别的日志信息,相应的代码为:

1
2
3
4
5
6
7
@Configuration
public class FullLogConfiguration {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

然后在HelloService接口上,请注意该接口被@FeignClient注解修饰,在注解内添加一个configuration属性,值就是前面定义的FullLogConfiguration类的字节码信息。

最后,我们可以重启服务注册中心、feign-consumer项目,然后在浏览器地址栏中访问http://localhost:3001/feign-consumer2链接,然后可以看到feign-consumer项目的控制台输出如下信息:

这就说明上面对于Feign客户端中HelloService接口的日志FULL级别设置就已经生效了。

不过现在有一个问题就是Feign的Logger级别有哪些呢?看一下源码就知道了,如下所示:

1
2
3
4
5
public static enum Level {
NONE,
BASIC,
HEADERS,
FULL;

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

好了,关于声明式服务调用—SpringCloud Feign组件的基础使用就学习到这里,后续会进一步学习和它相关的信息。