1. 前言
在 spring cloud 各种组件中,我最早接触的就是 open feign,但从来没有讲过它。原因是因为觉得它简单,无非就是个服务调用,在代码层面上也很简单,没有啥可说的。
但为什么今天来讲呢:
- 服务调用看起来简单,但实则是微服务治理中很重要的一环。我们现在微服务有上百个,如何提高微服务之间调用的稳定性,是老大难的问题。网络或高并发等原因,几乎每天都有个别报错是 feign 调用的。
- open feign 其实是封装了负载均衡、熔断等其他组件的,掌握它是有难度的。
不过这里着重讲feign超时、重试的部分,其他的功能可以看feign官方文档。
1. feign 与 openfeign
feign
是 netflix 公司写的,是 spring cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,是 spring cloud 中的第一代负载均衡客户端。后来 netflix 讲 feign 开源给了 spring cloud 社区,也随之停更。
openfeig
是 spring cloud 自己研发的,在 feign
的基础上支持了 spring MVC 的注解,如 @RequesMapping 等等。是 spring cloud中的第二代负载均衡客户端。
虽然 feign 停更了,之前我也介绍过 dobbo 这类替代产品。但在服务调用这个领域,open feign 还是有它的一席之地。
本文中讲的都是 openfeign,有些地方就简写成feign了。
2. spring cloud 版本更迭
先讲讲 spring cloud 的版本迭代吧。在2020年之前,spring cloud 等版本号是按照伦敦地铁站号命名的(ABCDEFGH):
- Angle
- Brixton
- Camden
- Dalston
- Edgware
- Finchley
- GreenWich
- Hoxton
但从2020年开始,版本号开始以年份命名,如:2020.0.1。
spring cloud 与 spring boot 版本的对应关系如下:
spring cloud 版本 | spring boot 版本 |
---|---|
2022.x | 3.0 |
2021.x | 2.6.x、2.7.x(2021.0.3+) |
2020.x | 2.4.x、2.5.x(2020.0.3+) |
Hoxton | 2.2.x、2.3.x(SR5+) |
GreenWich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
3. open feign 版本更迭
在 2020.x 版本之前,open feign 默认依赖了 hystrix、ribbon。
但从 2020.x 版本开始,open feign 就不再依赖 hystrix、ribbon了。
- 熔断:可以自己选择熔断组件,不过需要额外引入依赖,如:resilience4j、sentinel。
- 负载均衡:该用 spring cloud loadbalancer 替代 ribbon。
2. 示例代码
本着实践出真知的原则,我们还是创建个项目试验一下。这一章节就是把示例代码的核心代码列出来。代码还是以 openfeign 的低版本为主,spring cloud 版本为 Hoxton。
示例代码是个多模块的项目,为了构建一个简单的 feign 服务调用的场景,构建下面3个子模块:
- eureka-server:为 feign 服务调用提供注册中心。
- demo1-app:http服务,对外提供接口,供 demo2-app 调用。
- demo2-app:http服务,对外提供接口,该接口调用 demo1-app 的接口。
2.1. parent
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>pers.kerry</groupId>
<artifactId>feign-service</artifactId>
<version>${revision}</version>
<name>feign-service</name>
<description>feign-service</description>
<packaging>pom</packaging>
<properties>
<revision>0.0.1-SNAPSHOT</revision>
<java.version>8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-boot-starter.version>2.3.2.RELEASE</spring-boot-starter.version>
<flatten-maven-plugin.version>1.2.7</flatten-maven-plugin.version>
<lombok.version>1.18.24</lombok.version>
<resilience4j.version>0.13.2</resilience4j.version>
</properties>
<modules>
<module>eureka-server</module>
<module>demo1-app</module>
<module>demo2-app</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>clean</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2.2. eureka-server
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pers.kerry</groupId>
<artifactId>feign-service</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>pers.kerry</groupId>
<artifactId>eureka-server</artifactId>
<name>eureka-server</name>
<description>eureka-server</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. EurekaServerApplication
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
3. application.yml
server:
port: 8000
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
2.3. demo1-app
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pers.kerry</groupId>
<artifactId>feign-service</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>pers.kerry</groupId>
<artifactId>demo1-app</artifactId>
<name>demo1-app</name>
<description>demo1-app</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Demo1AppApplication
@SpringBootApplication
@EnableDiscoveryClient
public class Demo1AppApplication {
public static void main(String[] args) {
SpringApplication.run(Demo1AppApplication.class, args);
}
}
3. DemoController
@RestController
@RequestMapping
@Slf4j
public class DemoController {
@GetMapping("hello")
public String hello(@RequestParam Integer seconds) {
if (seconds < 0) {
throw new RuntimeException("时间不能为负数");
}
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("app1: 你好!");
return "hello";
}
}
4. application.yml
server:
port: 8001
spring:
application:
name: app1
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
2.4. demo2-app
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pers.kerry</groupId>
<artifactId>feign-service</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>pers.kerry</groupId>
<artifactId>demo2-app</artifactId>
<name>demo2-app</name>
<description>demo2-app</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Demo2AppApplication
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class Demo2AppApplication {
public static void main(String[] args) {
SpringApplication.run(Demo2AppApplication.class, args);
}
}
3. DemoController
@RestController
@RequestMapping("feign")
@AllArgsConstructor
@Slf4j
public class DemoController {
private final HelloFeign helloFeign;
@GetMapping("hello")
public String hello(@RequestParam Integer seconds) {
log.info("app2: 你好!");
return helloFeign.hello(seconds);
}
}
4. HelloFeign
@FeignClient(name = "app1")
public interface HelloFeign {
@GetMapping("hello")
String hello(@RequestParam Integer seconds);
}
5. application.yml
server:
port: 8002
spring:
application:
name: app2
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
3. feign 超时、重试
3.1. feign 超时
1. 设置
当我们在 demo2-app 的 application.yml 文件中仅添加 feign 的配置:
server:
port: 8002
spring:
application:
name: app2
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
feign:
client:
config:
default:
connect-timeout: 1000
read-timeout: 2500
spring 配置类中注册bean:
@Bean
public Retryer retryer(){
return new Retryer.Default(100,1000,3);
}
feign 的重试是通过 retryer
属性实现的,但如果需要自定义重试策略,则需要写代码注册 bean。
按照 Retryer 类构造方法中参数顺序依次为:
period
: 初始重试间隔 ,默认实现值是 100 msmaxPeriod
: 最大重试间隔 ,默认实现值是 1000 msmaxAttempts
: 最大重试次数,初始调用算一次,默认实现值是 5
2. 测试用例1
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印1次数(“app1: 你好!”)
- app2 成功返回 “hello”
3. 测试用例2
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印3次数(“app1: 你好!”)后
- app2 接口报错。错误:
feign.RetryableException
4. 分析
在配置中,我们设置请求处理时间(readTimeout)为2.5秒,失败后重试2次(减去初始调用的1次)。
在测试用例1中,因为设置 app1 处理时间在2秒,没有超过2.5秒,所以正常请求成功,app1只打印了1次。
在测试用例2中,因为设置 app1 处理时间在3秒,超过了2.5秒,单次请求失败,触发了失败重试机制。首次执行了1次,又重试了2次,所以一共有3次调用,app1 共打印了3次。
5. feign配置
connect-timeout
: 请求连接的超时时间(毫秒)read-timeout
: 请求处理的超时时间(毫秒)retryer
: 重试的实现类(如:feign.Retryer.Default)。如果不配置,则默认不重试
6. 局部配置
其实正常的配置前缀应该叫 feign.config.client.${feignName}
。可以针对不同的 feign 调用服务(@FeignClient 中的 name 属性值),配置不同的策略。上述的 feign.config.client.default
是设置默认配置。
如下列可针对 app1、appx 配置不同策略:
feign:
client:
config:
app1:
connect-timeout: 1000
read-timeout: 2500
retryer: feign.Retryer.Default
appx:
connect-timeout: 1000
read-timeout: 4500
retryer: pers.kerry.demo2app.config.AppXRetryer
3.2. feign 重试(retryer)
1. 全局配置
上述中在 配置类(@Configuration) 中注册 Retryer Bean,就是全局配置,所有服务都走这同一个策略。如下:
@SpringBootConfiguration
public class AppFeignConfig {
@Bean
public Retryer retryer(){
return new Retryer.Default(100,1000,3);
}
}
要注意的是,一旦在注册了 bean,就算 feign.config.client.${feignName}.retryer
为空,也不会关闭重试策略,依然生效。所以这种方式要慎重!
2. 局部配置(指定Bean配置类)
和上面的例子很像,同样在类中声明 Retryer Bean,但并非在配置类中,只是作为 feign client 指定的逻辑上“配置类”。如下:
public class AppFeignConfig {
@Bean
public Retryer retryer(){
return new Retryer.Default(100,1000,3);
}
}
然后在 HelloFeign.java 中指定配置类:
@FeignClient(name = "app1",configuration = AppFeignConfig.class)
public interface HelloFeign {
@GetMapping("hello")
String hello(@RequestParam Integer seconds);
}
此时 feign.config.client.${feignName}.retryer
可以为空,因为读的是 @FeignClient 的配置了。
3. 局部配置(指定类路径)
可自定义类继承 Retryer默认类(feign.Retryer.Default),可通过设置默认构造方法,来定义重试规则,如下:
pers.kerry.demo2app.config.AppXRetryer.java
public class AppXRetryer extends Retryer.Default {
private static final int maxAttempts = 2;
private static final long period = 100;
private static final long maxPeriod = 1500;
public AppXRetryer() {
super(period, maxPeriod, maxAttempts);
}
@Override
public Retryer clone() {
return new AppXRetryer();
}
}
其在 application.yml 上配置的方式是:
feign:
client:
config:
default:
connect-timeout: 1000
read-timeout: 1500
retryer: pers.kerry.demo2app.config.AppXRetryer
4. ribbon 超时、重试
1. 设置
当我们在 demo2-app 的 application.yml 文件中仅添加 ribbon 的配置:
server:
port: 8002
spring:
application:
name: app2
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
feign:
hystrix:
enabled: false
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2500
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 0
2. 测试用例1
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印1次数(“app1: 你好!”)
- app2 成功返回 “hello”
3. 测试用例2
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印4次数(“app1: 你好!”)后
- app2 接口报错。错误:
feign.RetryableException
4. 分析
在配置中,我们设置请求处理时间(ReadTimeout)为2.5秒,失败后重试3次。
在测试用例1中,因为设置 app1 处理时间在2秒,没有超过2.5秒,所以正常请求成功,app1只打印了1次。
在测试用例2中,因为设置 app1 处理时间在3秒,超过了2.5秒,单次请求失败,触发了失败重试机制。因为首次执行了1次,又重试了3次,所以一共有4次调用,app1 共打印了4次。
5. ribbon 配置
ConnectTimeout
: 请求连接的超时时间(毫秒)ReadTimeout
: 请求处理的超时时间(毫秒)MaxAutoRetries
: 同一实例最大重试次数,不包括首次调用。默认值为0MaxAutoRetriesNextServer
: 同一个服务其他实例的最大重试次数,不包括第一次调用的实例。默认值为1OkToRetryOnAllOperations
: 是否所有操作都允许重试。默认值为false,即只在GET协议上重试所有错误ServerListRefreshInterval
: Ribbon更新服务注册列表的频率(毫秒)
6. 局部配置
可针对不用的 feign 调用服务(@FeignClient 中的 name 属性值),配置不同的策略。如,下列可针对 app1、appx 配置不同策略:
app1:
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2500
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 0
appn:
ribbon:
ConnectTimeout: 1000
ReadTimeout: 4500
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 3
5. feign、ribbon 比较
1. 比较
feign 的配置策略更丰富,至少 idea 会有提示。
但在失败重试的方向上,ribbon功能更强大。不仅是配置起来更简单,而且支持跨服务重试,这个在实际应用中很重要。毕竟当某个服务因高并发而短暂阻塞时,最好的解决方法就是引流到其他服务上重试。
2. 优先级
当上述 application 文件中,feign、ribbon 同时开启配置如下:
feign:
hystrix:
enabled: false
client:
config:
default:
connect-timeout: 1000
read-timeout: 1500
retryer: pers.kerry.demo2app.config.AppXRetryer
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2500
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 0
在测试时发现无论超时还是重试,当前生效的只有 feign 的配置。
可见默认情况下,feign 配置的优先级要高于 ribbon。
因为有一个 feign.client.default-to-properties
的属性,其作用是初始化对象获取属性的优先级顺序。因为默认值为true,即 feign配置的优先级最高。如果手动设置为 false,则可以以 ribbon 的配置生效。
6. 熔断 hystrix(feign低版本)
hystrix 是由 netflix 开源的一款容错框架,包含隔离(线程池隔离、信号量隔离)、熔断、降级回退和缓存容错、缓存、批量处理请求、主从分担等常用功能。
feign本身支持 hystrix,默认是关闭 hystrix 的,需要在配置文件中开启 feign.hystrix.enabled=true
,默认值为 false。
6.1. hystrix 测试
因为 feign 默认就引入了 hystrix,在开启 feign.hystrix 后,只需要设置 hystrix 的配置就可以了。如下面的配置,再测试一下:
feign:
client:
config:
default:
connect-timeout: 1000
read-timeout: 1500
retryer: pers.kerry.demo2app.config.AppXRetryer
default-to-properties: true
hystrix:
enabled: true
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 2500
1. 测试用例1
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印1次数(“app1: 你好!”)
- app2 成功返回 “hello”
2. 测试用例2
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印2次数(“app1: 你好!”)后
- app2 接口报错。错误:
com.netflix.hystrix.exception.HystrixRuntimeException
3. 测试用例3
调用接口:(GET) http://localhost:8002/feign/h...
执行结果按照时间顺序是:
- app1 打印1次数(“app1: 你好!”)
- app2 接口报错。错误:
com.netflix.hystrix.exception.HystrixRuntimeException
- app1 再打印1次数(“app1: 你好!”)
如果不考虑 hystrix 的因素,当请求 seconds 值为3时,应该是和值为2时一样,在重试1次后再中断请求报错。
但由于3大于 hystrix 设置的超时时间2.5,在第一次请求时就触发了熔断报错。不过由于 feign 的重试机制,依然再重试了1次,但属于无效的重试,毕竟app2接口的http请求已经终结了。
所以如果需要开启 hystrix 熔断,各自超时时间的值,需要好好搭配一下。
6.2. hystrix 配置
当开启 feign.hystrix
后,可参考下列默认配置。
hystrix:
command:
default:
execution:
timeout:
enabled: true # 开启超时熔断
isolation:
strategy: THREAD
semaphore:
maxConcurrentRequests: 100 # 默认最大100个信号量并发,业务可根据具体情况调整(strategy=semaphore时生效)
thread:
timeoutInMilliseconds: 10000 # 默认熔断时间10秒,需要大于ribbon的retry*timeout
#熔断策略
circuitBreaker:
enabled: true # 启用熔断
requestVolumeThreshold: 20 # 度量窗口内请求量阈值,熔断前置条件,默认20
errorThresholdPercentage: 50 # 错误阈值比例,超过则触发熔断,默认50%
sleepWindowInMilliseconds: 5000 # 等待时间后重新检查请求,默认5秒
threadpool:
default:
coreSize: 10 # 核心数量,默认10,可根据实际业务调整
maximumSize: 10 # 最大数量,默认10,可根据实际业务调整
allowMaximumSizeToDivergeFromCoreSize: true # 是否允许从coreSize扩充到maximumSize
maxQueueSize: 1000 # 队列最大数量,不支持动态配置
queueSizeRejectionThreshold: 500 # 队列数量阈值,可动态配置
keepAliveTimeMinutes: 2
实际的配置项可看 hystrix 官方文档。本文要特别强调的是:
hystrix 超时时长 > (ribbon 超时时长 ribbon 重试次数) or (feign 超时时长 feign 重试次数 )
7. 熔断 sentinel(feign高版本)
1. 配置
先在 demo2-app 的pom中引入sentinel 的依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
启动本地 sentinel dashboard 服务,便于观察 feign的熔断策略。
在 application 文件中加上对dashboard的注册,并且开启 feign.sentinel:
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:9999
port: 8721
feign:
sentinel:
enabled: true
当打开 dashboard,发现feign中对应hello 的方法,对应的资源名称是 GET:http://app1/hello
,如果不开启 feign.sentinel.enabled,该资源不会出来。
这样,我们就可以基于dashboard上的该资源,对其创建规则了。
为了方便文章展示,这里通过代码中注册bean的方式来创建规则,和直接在dashboard中配置是一样的。
@SpringBootConfiguration
public class AppFeignConfig {
@Bean
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule("GET:http://app1/hello")
.setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType())
.setCount(5)
.setMinRequestAmount(2)
.setStatIntervalMs(1000)
.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
这里配置了一个熔断器的规则,当一秒内有5个报错,就回触发熔断。当10秒后会半开,半开期如果下一次依然报错,则再次熔断。可如果正常了,就关闭熔断器。
feign中添加降级类:
@FeignClient(name = "app1", fallback = FallbackHelloFeign.class)
... ...
FallbackHelloFeign 类中定义降级方法:
@Component
public class FallbackHelloFeign implements HelloFeign{
@Override
public String hello(Integer seconds) {
return "fallback hello";
}
}
controller 修改一下,方便测试验证:
@GetMapping("hello")
public String hello(@RequestParam Integer seconds) throws Exception{
for (int i = 0; i < seconds; i++) {
helloFeign.hello(-1);
}
log.info("app2: 你好!");
return helloFeign.hello(0);
}
2. 验证
当服务启动后,在 dashboard 中能看到之前在 bean 中定义的熔断规则。
第1次调用,传入 seconds=3,因为小于阈值,正常返回 hello。
第2次测试,传入 seconds=6,此时大于阈值,触发熔断,返回 fallback hello。
因为熔断了,在熔断后的10秒内,就算传入 seconds=0,不产生报错,也只会返回 fallback hello。
当10秒后,传入 seconds=1,虽然就报错1次,但由于是半开阶段,再次熔断。
再等10秒后,再次传入 seconds=0,此时因为不报错了,发现熔断解除了,恢复到刚开始。
3. 动态生成规则
重新回到之前的资源名称 GET:http://app1/hello
,可以发现是有规律的。我们完全可以通过反射,拼接获取到feign中每个方法的资源名称。
有了资源名称,就可以动态加载到 sentinel 的规则中。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。