深入學習spring cloud gateway 限流熔斷

目前,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

至此給大傢介紹瞭Spring Cloud Gateway中斷路器跟限流器使用。更多相關spring cloud gateway 限流熔斷內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!