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 的規則中。