目前,Spring Cloud Gateway是僅次于Spring Cloud Netflix的第二個最受歡迎的Spring Cloud項目(就GitHub上的星級而言)。它是作為Spring Cloud系列中Zuul代理的繼任者而創建的。該項目提供了用于微服務體系結構的API網關,并基于反應式Netty和Project Reactor構建。它旨在提供一種簡單而有效的方法來路由到API并解決諸如安全性,監視/度量和彈性之類的普遍關注的問題。
基于redis限流
Spring Cloud Gateway為您提供了許多功能和配置選項。今天,我將集中討論網關配置的一個非常有趣的方面-速率限制。速率限制器可以定義為一種控制網絡上發送或接收的流量速率的方法。我們還可以定義幾種類型的速率限制。Spring Cloud Gateway當前提供了一個Request Rate Limiter,它負責將每個用戶每秒限制為N個請求。與Spring Cloud Gateway一起 使用時RequestRateLimiter,我們可能會利用Redis。Spring Cloud實現使用令牌桶算法做限速。該算法具有集中式存儲桶主機,您可以在其中對每個請求獲取令牌,然后將更多的令牌緩慢滴入存儲桶中。如果存儲桶為空,則拒絕該請求。
項目演示源碼地址:
https://github.com/1ssqq1lxr/SpringCloudGatewayTest
- 引入maven依賴
## spring cloud依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<JAVA.version>11</java.version>
<spring-cloud.version>Hoxton.RC2</spring-cloud.version>
</properties>
<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>
</dependencies>
</dependencyManagement>
## gateway 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mockserver</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>3.10.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>junit-benchmarks</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
- 限流器配置
使用Spring Cloud Gateway默認請求限流GatewayFilter(
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter)。使用默認的Redis限流器方案,你可以通過自定義keyResolver類去決定Redis限流key的生成,下面舉常用幾個例子:
- 根據用戶: 使用這種方式限流,請求路徑中必須攜帶userId參數
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
- 根據獲uri
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
由于我們已經討論了Spring Cloud Gateway速率限制的一些理論方面,因此我們可以繼續進行實施。首先,讓我們定義主類和非常簡單的KeyResolverbean,它始終等于一個。
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just("1");
}
}
Gateway默認使用
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter 限流器。 現在,如通過模擬Http請求,則會收到以下響應。它包括一些特定的header,其前綴為x-ratelimit。
- x-ratelimit-burst-capacity:最大令牌值,
- x-ratelimit-replenish-rate:填充的速率值,
- x-ratelimit-remaining:剩下可請求數。
yaml配置:
server:
port: ${PORT:8085}
spring:
application:
name: gateway-service
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: account-service
uri: http://localhost:8091
predicates:
- Path=/account/**
filters:
- RewritePath=/account/(?.*), /${path}
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
- Redis限流器實現
關鍵源碼如下:
// routeId也就是我們的服務路由id,id就是限流的key
public Mono<Response> isAllowed(String routeId, String id) {
// 會判斷RedisRateLimiter是否初始化了
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
}
// 獲取routeId對應的限流配置
Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
if (routeConfig == null) {
throw new IllegalArgumentException("No Configuration found for route " + routeId);
}
// 允許用戶每秒做多少次請求
int replenishRate = routeConfig.getReplenishRate();
// 令牌桶的容量,允許在一秒鐘內完成的最大請求數
int burstCapacity = routeConfig.getBurstCapacity();
try {
// 限流key的名稱(request_rate_limiter.{localhost}.timestamp,request_rate_limiter.{localhost}.tokens)
List<String> keys = getKeys(id);
// The arguments to the LUA script. time() returns unixtime in seconds.
List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", "1");
// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
// 執行LUA腳本
Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
// .log("redisratelimiter", Level.FINER);
return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
.reduce(new ArrayList<Long>(), (longs, l) -> {
longs.addAll(l);
return longs;
}) .map(results -> {
boolean allowed = results.get(0) == 1L;
Long tokensLeft = results.get(1);
Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
if (log.isDebugEnabled()) {
log.debug("response: " + response);
}
return response;
});
}
catch (Exception e) {
log.error("Error determining if user allowed from redis", e);
}
return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
}
- 測試Redis限流器
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@RunWith(SpringRunner.class)
public class GatewayRateLimiterTest {
private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);
@Rule
public TestRule benchmarkRun = new BenchmarkRule();
@ClassRule
public static MockServerContainer mockServer = new MockServerContainer();
@ClassRule
public static GenericContainer redis = new GenericContainer("redis:5.0.6").withExposedPorts(6379);
@Autowired
TestRestTemplate template;
@BeforeClass
public static void init() {
System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());
System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\{path}");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "10");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "20");
System.setProperty("spring.redis.host", "localhost");
System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));
new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())
.when(HttpRequest.request()
.withPath("/1"))
.respond(response()
.withBody("{"id":1,"number":"1234567890"}")
.withHeader("Content-Type", "application/json"));
}
@Test
@BenchmarkOptions(warmupRounds = 0, concurrency = 6, benchmarkRounds = 600)
public void testAccountService() {
ResponseEntity<Account> r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, 1);
LOGGER.info("Received: status->{}, payload->{}, remaining->{}", r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));
// Assert.assertEquals(200, r.getStatusCodeValue());
// Assert.assertNotNull(r.getBody());
// Assert.assertEquals(Integer.valueOf(1), r.getBody().getId());
// Assert.assertEquals("1234567890", r.getBody().getNumber());
}
}
執行Test類: 發現超過20之后會被攔截返回429,運行過程中隨著令牌的放入會不斷有請求成功。
14:20:32.242 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[18]
14:20:32.242 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[16]
14:20:32.242 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[14]
14:20:32.242 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[15]
14:20:32.242 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[17]
14:20:32.242 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[19]
14:20:32.294 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[15]
14:20:32.297 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[19]
14:20:32.304 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[18]
14:20:32.308 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[16]
14:20:32.309 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[17]
14:20:32.312 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[14]
14:20:32.320 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[13]
14:20:32.326 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[12]
14:20:32.356 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[7]
14:20:32.356 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[10]
14:20:32.361 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[6]
14:20:32.363 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[8]
14:20:32.384 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[4]
14:20:32.384 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[11]
14:20:32.386 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[5]
14:20:32.390 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[9]
14:20:32.391 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[3]
14:20:32.392 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[2]
14:20:32.403 --- [pool-2-thread-6] : Received: status->429, payload->null, remaining->[0]
14:20:32.403 --- [pool-2-thread-4] : Received: status->429, payload->null, remaining->[0]
........
14:20:33.029 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[9]
14:20:33.033 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[8]
14:20:33.033 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[7]
14:20:33.037 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[6]
14:20:33.039 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[5]
14:20:33.046 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[4]
14:20:33.052 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]
14:20:33.058 --- [pool-2-thread-6] : Received: status->429, payload->null, remaining->[0]
14:20:33.058 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[2]
14:20:33.060 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]
14:20:33.081 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[1]
14:20:33.082 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[0]
14:20:33.084 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[3]
14:20:33.088 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]
如果默認的限流器不能夠滿足使用,可以通過繼承AbstractRateLimiter實現自定義限流器,然后通過RouteLocator方式去注入攔截器。
Resilience4J熔斷器
- 引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
- Resilience4J 斷路器介紹
- 三個一般性狀態CLOSED:關閉狀態,放過所有請求,記錄請求狀態。OPEN:打開,異常請求達到閥值數量時,開啟熔斷,拒絕所有請求。HALF_OPEN:半開,放開一定數量的請求,重新計算錯誤率。
- 兩個特定狀態DISABLED:禁用FORCED_OPEN:強開
- 狀態之間轉換啟動時斷路器為CLOSE狀態,在達到一定請求量之后計算請求失敗率,達到或高于指定失敗率后,斷路進入open狀態,阻攔所有請求,開啟一段時間(自定義)時間后,斷路器變為halfOpen狀態,重新計算請求失敗率。halfOpen錯誤率低于指定失敗率后,斷路進入close狀態,否則進入open狀態。
狀態轉換
- 通過Resilience4J啟用Spring Cloud Gateway斷路器
要啟用構建在Resilience4J之上的斷路器,我們需要聲明一個Customizer傳遞了的bean
ReactiveResilience4JCircuitBreakerFactory。可以非常簡單地去配置設置,下面使用默認配置進行測試
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
//統計失敗率的請求總數
.slidingwindowsize(5)
//在半開狀態下請求的次數
.permittedNumberOfCallsInHalfOpenState(5)
//斷路器打開的成功率
.failureRateThreshold(50.0F)
//斷路器打開的周期
.waitDurationInOpenState(Duration.ofMillis(30))
//屬于慢請求的周期
.slowCallDurationThreshold(Duration.ofMillis(200))
//慢請求打開斷路器的成功率
.slowCallRateThreshold(50.0F)
.build())
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build()).build());
}
- 測試Resilience4J斷路器
使用默認配置進行測試
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.slowCallDurationThreshold(Duration.ofMillis(200))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build())
.build());
}
執行下面Test用例
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@RunWith(SpringRunner.class)
public class GatewayCircuitBreakerTest {
private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);
@Rule
public TestRule benchmarkRun = new BenchmarkRule();
@ClassRule
public static MockServerContainer mockServer = new MockServerContainer();
@Autowired
TestRestTemplate template;
final Random random = new Random();
int i = 0;
@BeforeClass
public static void init() {
System.setProperty("logging.level.org.springframework.cloud.gateway.filter.factory", "TRACE");
System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());
System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\{path}");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "CircuitBreaker");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.name", "exampleSlowCircuitBreaker");
// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallDurationThreshold", "100");
// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallRateThreshold", "9.0F");
// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.fallbackUri", "forward:/fallback/account");
MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort());
client.when(HttpRequest.request()
.withPath("/1"))
.respond(response()
.withBody("{"id":1,"number":"1234567890"}")
.withHeader("Content-Type", "application/json"));
// client.when(HttpRequest.request()
// .withPath("/2"), Times.exactly(3))
// .respond(response()
// .withBody("{"id":2,"number":"1"}")
// .withDelay(TimeUnit.SECONDS, 1000)
// .withHeader("Content-Type", "application/json"));
client.when(HttpRequest.request()
.withPath("/2"))
.respond(response()
.withBody("{"id":2,"number":"1234567891"}")
.withDelay(TimeUnit.SECONDS, 200)
.withHeader("Content-Type", "application/json"));
}
@Test
@BenchmarkOptions(warmupRounds = 0, concurrency = 1, benchmarkRounds = 600)
public void testAccountService() {
int gen = 1 + (i++ % 2);
ResponseEntity<Account> r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, gen);
LOGGER.info("{}. Received: status->{}, payload->{}, call->{}", i, r.getStatusCodeValue(), r.getBody(), gen);
}
}
請求日志如下:當請求達到100次時候,此時失敗率為50% 這時候系統開啟斷路器返回503!
20:07:29.281 --- [pool-2-thread-1] : 91. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:07:30.297 --- [pool-2-thread-1] : 92. Received: status->504, payload->Account(id=null, number=null), call->2
20:07:30.316 --- [pool-2-thread-1] : 93. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:07:31.328 --- [pool-2-thread-1] : 94. Received: status->504, payload->Account(id=null, number=null), call->2
20:07:31.345 --- [pool-2-thread-1] : 95. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:07:32.359 --- [pool-2-thread-1] : 96. Received: status->504, payload->Account(id=null, number=null), call->2
20:07:32.385 --- [pool-2-thread-1] : 97. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:07:33.400 --- [pool-2-thread-1] : 98. Received: status->504, payload->Account(id=null, number=null), call->2
20:07:33.414 --- [pool-2-thread-1] : 99. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:07:34.509 --- [pool-2-thread-1] : 100. Received: status->504, payload->Account(id=null, number=null), call->2
20:07:34.525 --- [pool-2-thread-1] : 101. Received: status->503, payload->Account(id=null, number=null), call->1
20:07:34.533 --- [pool-2-thread-1] : 102. Received: status->503, payload->Account(id=null, number=null), call->2
20:07:34.539 --- [pool-2-thread-1] : 103. Received: status->503, payload->Account(id=null, number=null), call->1
20:07:34.545 --- [pool-2-thread-1] : 104. Received: status->503, payload->Account(id=null, number=null), call->2
20:07:34.552 --- [pool-2-thread-1] : 105. Received: status->503, payload->Account(id=null, number=null), call->1
20:07:34.566 --- [pool-2-thread-1] : 106. Received: status->503, payload->Account(id=null, number=null), call->2
20:07:34.572 --- [pool-2-thread-1] : 107. Received: status->503, payload->Account(id=null, number=null), call->1
20:07:34.576 --- [pool-2-thread-1] : 108. Received: status->503, payload->Account(id=null, number=null), call->2
20:07:34.580 --- [pool-2-thread-1] : 109. Received: status->503, payload->Account(id=null, number=null), call->1
20:07:34.586 --- [pool-2-thread-1] : 110. Received: status->503, payload->Account(id=null, number=null), call->2
20:07:34.591 --- [pool-2-thread-1] : 111. Received: status->503, payload->Account(id=null, number=null), call->1
這時候我們修改下配置
@BeforeClass
public static void init() {
System.setProperty("logging.level.org.springframework.cloud.gateway.filter.factory", "TRACE");
System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());
System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\{path}");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "CircuitBreaker");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.name", "exampleSlowCircuitBreaker");
// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallDurationThreshold", "100");
// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallRateThreshold", "9.0F");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.fallbackUri", "forward:/fallback/account");
MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort());
client.when(HttpRequest.request()
.withPath("/1"))
.respond(response()
.withBody("{"id":1,"number":"1234567890"}")
.withHeader("Content-Type", "application/json"));
client.when(HttpRequest.request()
.withPath("/2"), Times.exactly(3))
.respond(response()
.withBody("{"id":2,"number":"1"}")
.withDelay(TimeUnit.SECONDS, 1000)
.withHeader("Content-Type", "application/json"));
client.when(HttpRequest.request()
.withPath("/2"))
.respond(response()
.withBody("{"id":2,"number":"1234567891"}")
// .withDelay(TimeUnit.SECONDS, 200)
.withHeader("Content-Type", "application/json"));
}
新建一個回調接口,用于斷路器打開后請求的地址。
@RestController
@RequestMapping("/fallback")
public class GatewayFallback {
@GetMapping("/account")
public Account getAccount() {
Account a = new Account();
a.setId(2);
a.setNumber("123456");
return a;
}
}
使用默認設置時,前3次請求觸發斷路器回調,后面正常請求成功
20:20:23.529 --- [pool-2-thread-1] : 1. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:23.777 --- [pool-2-thread-1] : 2. Received: status->200, payload->Account(id=2, number=123456), call->2
20:20:23.808 --- [pool-2-thread-1] : 3. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:24.018 --- [pool-2-thread-1] : 4. Received: status->200, payload->Account(id=2, number=123456), call->2
20:20:24.052 --- [pool-2-thread-1] : 5. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:24.268 --- [pool-2-thread-1] : 6. Received: status->200, payload->Account(id=2, number=123456), call->2
20:20:24.301 --- [pool-2-thread-1] : 7. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:24.317 --- [pool-2-thread-1] : 8. Received: status->200, payload->Account(id=2, number=1234567891), call->2
20:20:24.346 --- [pool-2-thread-1] : 9. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:24.363 --- [pool-2-thread-1] : 10. Received: status->200, payload->Account(id=2, number=1234567891), call->2
20:20:24.378 --- [pool-2-thread-1] : 11. Received: status->200, payload->Account(id=1, number=1234567890), call->1
20:20:24.392 --- [pool-2-thread-1] : 12. Received: status->200, payload->Account(id=2, number=1234567891), call->2
20:20:24.402 --- [pool-2-thread-1] : 13. Received: status->200, payload->Account(id=1, number=1234567890), call->1
END
至此給大家介紹了Spring Cloud Gateway中斷路器跟限流器使用。