版本说明
org.springframework.boot
spring-boot-starter-parent
2.0.2.RELEASE
UTF-8
UTF-8
1.8
Finchley.RC2
Ribbon配置
全局配置
在application.properties中使用ribbon.
# 设置连接超时时间
ribbon.ConnectTimeout=500
# 设置读取超时时间
ribbon.ReadTimeout=2000
指定服务配置
在使用Spring Cloud Feign的时候,针对各个服务客户端进行个性化配置的方式与使用Spring Cloud Ribbon时的配置方式一样,都采用
# 设置针对hello-service服务的连接超时时间
hello-service.ribbon.ConnectTimeout=500
# 设置针对hello-service服务的读取超时时间
hello-service.ribbon.ReadTimeout=2000
重试机制
Spring Cloud Feign默认实现了重试机制,默认重试5次,通过以下配置来配置相关属性。
# 设置针对hello-service服务的连接超时时间
hello-service.ribbon.ConnectTimeout=500
# 设置针对hello-service服务的读取超时时间
hello-service.ribbon.ReadTimeout=2000
# 设置针对hello-service服务所有操作请求都进行重试
hello-service.ribbon.OkToRetryOnAllOperations=true
# 设置针对hello-service服务切换实例的重试次数
hello-service.ribbon.MaxAutoRetriesNextServer=2
# 设置针对hello-service服务的当前实例的重试次数
hello-service.ribbon.MaxAutoRetries=1
# 设置断路器超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2001
相关配置的说明
配置 | 说明 |
---|---|
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds | 断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。 |
hello-service.ribbon.ConnectTimeout | 请求连接的超时时间 |
hello-service.ribbon.ReadTimeout | 请求处理的超时时间 |
hello-service.ribbon.OkToRetryOnAllOperations | 是否对所有操作请求都进行重试 |
hello-service.ribbon.MaxAutoRetriesNextServer | 重试负载均衡其他的实例最大重试次数,不包括首次server,所有的提供者都重试失败,则返回失败 |
hello-service.ribbon.MaxAutoRetries | 同一台实例最大重试次数,不包括首次调用 |
注意:断路器的超时时间和ribbon的超时时间是两个不同的概念,断路器的超时时间必须大于ribbon的超时时间,不然不会触发重试,因为断路器会直接熔断。
Feign重试和Ribbon重试
二者的重试机制相互独立,并无联系。但是因为用了feign肯定会用到ribbon,所以feign的重试机制相对来说比较鸡肋。另外,很重要的一点,不论是Feign重试还是Ribbon重试,如果开启了请求重试机制,对于get请求不会有太大的问题,但是对于post或者put请求就可能出现不可预计的后果。比如接口没有做幂等性,类似于对于一次订单支付过程,如果用户重复多次点击支付按钮,或者是网络异常导致订单已经支付成功了但没有及时反馈给用户,用户再次点击支付按钮,就可能造成重复扣款,造成严重后果。所以建议慎重设置这个参数,我建议关闭Feign重试和Ribbon重试,并且服务接口做幂等性等处理。
如果开启的话,建议合理配置Hystrix的超时时间,在一些没必要的重试请求执行时,根据Hystrix的超时时间,快速失败,结束重试。
测试
第一步,修改hello-service服务,也就是eureka-client-vFinchley.Rc2工程,修改FeignController,添加一个API,并让它随机睡眠0-3000ms
@GetMapping("hello")
public String hello(){
System.out.println("hello");
try {
//休眠0-3000ms
int sleepTime = new Random().nextInt(3000);
Thread.sleep(sleepTime);
System.out.println(sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "hello";
}
第二步,修改feign-consumer服务,即feign-consumer-vFinchley.RC2工程,修改HelloService,添加对上面API的服务调用。
@GetMapping("feign/hello")
String testRetry();
第三步,修改FeignConsumerController,添加对testRetry()的调用
@GetMapping("testRetry")
public String testRetry(){
return helloService.testRetry();
}
第四步,修改application.yml配置文件,配置Feign的重试机制
spring:
application:
name: feign-consumer #为服务命名
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
hello-service:
ribbon:
# 配置首台服务器重试1次
MaxAutoRetries: 1
# 配置其他服务器重试两次
MaxAutoRetriesNextServer: 2
# 连接超时时间
ConnectTimeout: 500
# 请求处理时间
ReadTimeout: 2000
# 每个操作都开启重试机制
OkToRetryOnAllOperations: true
# 配置断路器超时时间,默认是1000(1秒)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2001
启动服务注册中心,即eureka-server-vFinchley.Rc2工程
启动服务提供者hello-service,即eureka-client-vFinchley.Rc2 工程
启动服务消费者feign-consumer,即feign-consumer-vFinchley.RC2工程
请求localhost:5001/testRetry,直到遇见服务超时即可。
可以看到我划线部分,第一次请求延迟时间为2628毫秒,请求超时,Feign客户端发起了请求重试,第二次请求延迟为1553毫秒,没有超时。Feign客户端在进行服务调用时经历了一次失败,但是通过重试机制,最终还是获得了请求结果。
Hystrix配置
全局配置
对于Hystrix的全局配置同Spring Cloud Ribbon的全局配置一样,直接使用它的默认配置前缀hystrix.command.default就可以进行设置,比如设置全局的超时时间
# 设置熔断超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
指定命令配置
配置方法和传统的Hystrix命令的参数配置相似,采用hystrix.command.
hystrix.command.testRetry.execution.isolation.thread.timeoutInMilliseconds=1000
在使用指定命令配置的时候,需要注意,由于方法名很有可能重复,这个时候相同方法名的Hystrix配置会共用,所以尽量避免方法重名。当然,也可以重写Feign.Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder实现。
禁用Hystrix
1.全局配置
# 关闭Hystrix功能
feign.hystrix.enabled=false
# 关闭熔断功能
hystrix.command.default.execution.timeout.enabled=false
2.针对某个服务禁用Hystrix
第一步,构建一个关闭Hystrix的配置类
@Configuration
public class DisabledHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
第二步,在需要关闭Hystrix功能的Feign客户端的服务绑定接口类,通过@FeignClient注解的configuration属性指定
@FeignClient(value = "hello-service", fallback = HelloServiceFallback.class, configuration = {DisabledHystrixConfiguration.class })
public interface HelloService {
@GetMapping("hello")
String hello();
@GetMapping("feign/hello1")
public User hello1(@RequestParam("name") String name, @RequestParam("age") int age);
@GetMapping("feign/hello2")
public User hello2(@RequestHeader("name") String name, @RequestHeader("age") int age);
@PostMapping("feign/hello3")
public User hello3(@RequestBody User user);
@GetMapping("feign/hello")
String testRetry();
}
此时全局的Hystrix是开启的,只是关闭了HelloService的Hystrix功能。
服务降级配置
第一步,为Feign客户端的定义接口编写一个具体的接口实现类,并重写每个方法的服务降级逻辑。
@Component//此注解不加无法扫描到这个类
public class HelloServiceFallback implements HelloService{
@Override
public String hello() {
return "error";
}
@Override
public User hello1(String name, int age) {
return new User(1,"error",1);
}
@Override
public User hello2(String name, int age) {
// TODO Auto-generated method stub
return new User(1,"error",1);
}
@Override
public User hello3(User user) {
// TODO Auto-generated method stub
return new User(1,"error",1);
}
@Override
public String testRetry() {
// TODO Auto-generated method stub
return "error";
}
}
第二步,在服务绑定接口中通过@FeignClient注解的fallback属性指定对应的服务降级实现类。
@FeignClient(value="hello-service",fallback=HelloServiceFallback.class)
public interface HelloService {
@GetMapping("hello")
String hello();
@GetMapping("feign/hello1")
public User hello1(@RequestParam("name") String name,@RequestParam("age") int age);
@GetMapping("feign/hello2")
public User hello2(@RequestHeader("name") String name,@RequestHeader("age") int age);
@PostMapping("feign/hello3")
public User hello3(@RequestBody User user);
@GetMapping("feign/hello")
String testRetry();
}
测试
只启动服务注册中心和服务消费者feign-consumer,即feign-consumer-vFinchley.RC2,不启动服务提供者hello-service。
访问http://localhost:5001/hello1,结果说明直接触发了服务降级。
User [id=1, name=error, age=1]
User [id=1, name=error, age=1]
User [id=1, name=error, age=1]
注意:feign.hystrix.enabled默认值为false,Feign默认关闭Hystrix熔断功能,需要先通过feign.hystrix.enabled=true配置开启。
其他配置
请求压缩
Spring Cloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗,只需要下面两个参数设置就可以开启请求与响应的压缩功能。
# 配置请求GZIP压缩
feign.compression.request.enabled=true
# 配置响应GZIP压缩
feign.compression.response.enabled=true
Spring Cloud Feign也对请求压缩支持更细致的设置。
# 配置请求GZIP压缩
feign.compression.request.enabled=true
# 配置压缩支持的MIME TYPE 默认值text/xml,application/xml,application/json
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置压缩数据大小的下限 默认值2048
feign.compression.request.min-request-size=2048
日志配置
Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign.Logger实例,我们可以利用该日志对象的DEBUG模式来帮助分析Feign的请求细节。可以使用logging.level.
logging.level.com.wya.springboot.service.HelloService=DEBUG
在application.yml文件中这样设置
logging:
level:
com:
wya:
springboot:
service:
HelloService: DEBUG
我的版本就可以直接输出日志了,老版本的话这样还不能实现对DEBUG日志的输出,因为Feign客户端默认的logging.level对象定义为NONE级别,该级别不会记录Feign调用过程中的信息,我们需要调整它的级别。
对于全局的日志级别,在主类直接加入Logger.Level的Bean创建
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
对于不同的Feign客户端指定配置类可以通过实现配置类来调整日志级别。
@Configuration
public class FullLoggerConfiguration{
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
然后通过@FeignClient注解的configuration属性指定。
@FeignClient(value="hello-service",fallback=HelloServiceFallback.class,configuration={FullLoggerConfiguration.class})
public interface HelloService {
@GetMapping("hello")
String hello();
@GetMapping("feign/hello1")
public User hello1(@RequestParam("name") String name,@RequestParam("age") int age);
@GetMapping("feign/hello2")
public User hello2(@RequestHeader("name") String name,@RequestHeader("age") int age);
@PostMapping("feign/hello3")
public User hello3(@RequestBody User user);
@GetMapping("feign/hello")
String testRetry();
}
再次请求http://localhost:5001/hello1接口我们就可以看到控制台打印请求的详细信息。
2018-09-12 15:48:07.170 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Application is null : false
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Application version is -1: false
2018-09-12 15:48:07.171 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2018-09-12 15:48:07.222 INFO 9036 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : The response status is 200
2018-09-12 15:48:08.349 INFO 9036 --- [nio-5001-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-09-12 15:48:08.349 INFO 9036 --- [nio-5001-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2018-09-12 15:48:08.366 INFO 9036 --- [nio-5001-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 17 ms
2018-09-12 15:48:08.578 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] ---> GET http://hello-service/feign/hello1?name=test1&age=22 HTTP/1.1
2018-09-12 15:48:08.578 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] ---> END HTTP (0-byte body)
2018-09-12 15:48:08.580 INFO 9036 --- [hello-service-1] s.c.a.AnnotationConfigApplicationContext : Refreshing SpringClientFactory-hello-service: startup date [Wed Sep 12 15:48:08 CST 2018]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1fde5d22
2018-09-12 15:48:08.638 INFO 9036 --- [hello-service-1] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-09-12 15:48:08.846 INFO 9036 --- [hello-service-1] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-09-12 15:48:08.865 INFO 9036 --- [hello-service-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-hello-service
2018-09-12 15:48:08.886 INFO 9036 --- [hello-service-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: hello-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-09-12 15:48:08.892 INFO 9036 --- [hello-service-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2018-09-12 15:48:08.917 INFO 9036 --- [hello-service-1] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-09-12 15:48:08.918 INFO 9036 --- [hello-service-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client hello-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service,current list of Servers=[192.168.1.227:2222],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:192.168.1.227:2222; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@58287100
2018-09-12 15:48:09.193 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] <--- HTTP/1.1 200 (614ms)
2018-09-12 15:48:09.193 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] content-type: application/json;charset=UTF-8
2018-09-12 15:48:09.193 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] date: Wed, 12 Sep 2018 07:48:09 GMT
2018-09-12 15:48:09.193 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] transfer-encoding: chunked
2018-09-12 15:48:09.193 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1]
2018-09-12 15:48:09.207 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] {"id":1,"name":"test1","age":22}
2018-09-12 15:48:09.208 DEBUG 9036 --- [hello-service-1] com.wya.springboot.service.HelloService : [HelloService#hello1] <--- END HTTP (32-byte body)
2018-09-12 15:48:09.246 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] ---> GET http://hello-service/feign/hello2 HTTP/1.1
2018-09-12 15:48:09.246 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] name: test2
2018-09-12 15:48:09.246 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] age: 22
2018-09-12 15:48:09.246 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] ---> END HTTP (0-byte body)
2018-09-12 15:48:09.268 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] <--- HTTP/1.1 200 (21ms)
2018-09-12 15:48:09.269 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] content-type: application/json;charset=UTF-8
2018-09-12 15:48:09.269 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] date: Wed, 12 Sep 2018 07:48:09 GMT
2018-09-12 15:48:09.269 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] transfer-encoding: chunked
2018-09-12 15:48:09.269 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2]
2018-09-12 15:48:09.270 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] {"id":2,"name":"test2","age":22}
2018-09-12 15:48:09.270 DEBUG 9036 --- [hello-service-2] com.wya.springboot.service.HelloService : [HelloService#hello2] <--- END HTTP (32-byte body)
2018-09-12 15:48:09.292 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] ---> POST http://hello-service/feign/hello3 HTTP/1.1
2018-09-12 15:48:09.293 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] Content-Type: application/json;charset=UTF-8
2018-09-12 15:48:09.293 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] Content-Length: 32
2018-09-12 15:48:09.293 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3]
2018-09-12 15:48:09.293 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] {"id":3,"name":"test1","age":22}
2018-09-12 15:48:09.293 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] ---> END HTTP (32-byte body)
2018-09-12 15:48:09.346 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] <--- HTTP/1.1 200 (52ms)
2018-09-12 15:48:09.346 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] content-type: application/json;charset=UTF-8
2018-09-12 15:48:09.346 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] date: Wed, 12 Sep 2018 07:48:09 GMT
2018-09-12 15:48:09.347 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] transfer-encoding: chunked
2018-09-12 15:48:09.347 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3]
2018-09-12 15:48:09.347 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] {"id":3,"name":"test1","age":22}
2018-09-12 15:48:09.347 DEBUG 9036 --- [hello-service-3] com.wya.springboot.service.HelloService : [HelloService#hello3] <--- END HTTP (32-byte body)
2018-09-12 15:48:09.895 INFO 9036 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
对于Feign的Logger级别有如下四种,可根据实际情况调整。
Feign原理与覆盖默认配置
推荐阅读:深入理解Feign之源码解析,这里我就不赘述了。