深入学习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 限流熔断内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringCloud-Alibaba-Sentinel服务降级,热点限流,服务熔断

    前言: 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一.一个服务常常会调用别的模块,可能是另外的一个远程服务.数据库,或者第三方 API 等.例如,支付的时候,可能需要远程调用银联提供的 API:查询某个商品的价格,可能需要进行数据库查询.然而,这个被依赖服务的稳定性是不能保证的.如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用 熔断策略 Sentin

  • 深入学习spring cloud gateway 限流熔断

    目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitHub上的星级而言).它是作为Spring Cloud系列中Zuul代理的继任者而创建的.该项目提供了用于微服务体系结构的API网关,并基于反应式Netty和Project Reactor构建.它旨在提供一种简单而有效的方法来路由到API并解决诸如安全性,监视/度量和弹性之类的普遍关注的问题. 基于Redis限流 Spring Cloud Gate

  • spring cloud gateway 限流的实现与原理

    在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击. 常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑.在一般应用服务器中,比如tomcat容器也是通过限制它的线程数来控制并发的:也有通过时间窗口的平均速度来控制流量.常见的限流纬度有比如通过Ip来限流.通过uri来限流.通过用户访问频次来限流. 一般限流都是在网关这一层做,比如Nginx.Openresty.kong.zuul.Spring

  • 详解Spring Cloud Gateway 限流操作

    开发高并发系统时有三把利器用来保护系统:缓存.降级和限流. API网关作为所有请求的入口,请求量大,我们可以通过对并发访问的请求进行限速来保护系统的可用性. 常用的限流算法比如有令牌桶算法,漏桶算法,计数器算法等. 在Zuul中我们可以自己去实现限流的功能 (Zuul中如何限流在我的书 <Spring Cloud微服务-全栈技术与案例解析>  中有详细讲解) ,Spring Cloud Gateway的出现本身就是用来替代Zuul的. 要想替代那肯定得有强大的功能,除了性能上的优势之外,Spr

  • Spring Cloud Gateway不同频率限流的解决方案(每分钟,每小时,每天)

    SpringCloud Gateway 简介 SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式. SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuu

  • spring cloud gateway整合sentinel实现网关限流

    这篇文章主要介绍了spring cloud gateway整合sentinel实现网关限流,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 说明: sentinel可以作为各微服务的限流,也可以作为gateway网关的限流组件. spring cloud gateway有限流功能,但此处用sentinel来作为替待. 说明:sentinel流控可以放在gateway网关端,也可以放在各微服务端. 1,以父工程为基础,创建子工程 2,添加pom依赖

  • Spring Cloud Gateway整合sentinel 实现流控熔断的问题

    目录 一.什么是网关限流: 二.gateway 整合 sentinel 实现网关限流: 三.sentinel 网关流控规则的介绍: 3.1.网关流控规则: 3.2.API 分组管理: 四.sentinel 网关流控实现的原理: 五.网关限流了,服务就安全了吗? 六.自定义流控异常消息: 一.什么是网关限流: 在微服务架构中,网关层可以屏蔽外部服务直接对内部服务进行调用,对内部服务起到隔离保护的作用,网关限流,顾名思义,就是通过网关层对服务进行限流,从而达到保护后端服务的作用. Sentinel

  • 浅析Spring Cloud Gateway中的令牌桶限流算法

    目录 前言 回顾限流算法 计数器/时间窗口法 漏桶法 令牌桶法 主要逻辑分析 前言 在一个分布式高并发的系统设计中,限流是一个不可忽视的功能点.如果不对系统进行有效的流量访问限制,在双十一和抢票这种流量洪峰的场景下,很容易就会把我们的系统打垮.而作为系统服务的卫兵的网关组件,作为系统服务的统一入口,更需要考虑流量的限制,直接在网关层阻断流量比在各个系统中实现更合适.Spring Cloud Gateway的实现中,就提供了限流的功能,下面主要分析下Spring Cloud Gateway中是如何

  • 阿里Sentinel支持Spring Cloud Gateway的实现

    1. 前言 4月25号,Sentinel 1.6.0 正式发布,带来 Spring Cloud Gateway 支持.控制台登录功能.改进的热点限流和注解 fallback 等多项新特性,该出手时就出手,紧跟时代潮流,昨天刚发布,今天我就要给大家分享下如何使用! 2. 介绍(本段来自Sentinel文档) Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑: Gatewa

  • Spring Cloud GateWay 路由转发规则介绍详解

    Spring在因Netflix开源流产事件后,在不断的更换Netflix相关的组件,比如:Eureka.Zuul.Feign.Ribbon等,Zuul的替代产品就是SpringCloud Gateway,这是Spring团队研发的网关组件,可以实现限流.安全认证.支持长连接等新特性. Spring Cloud Gateway Spring Cloud Gateway是SpringCloud的全新子项目,该项目基于Spring5.x.SpringBoot2.x技术版本进行编写,意在提供简单方便.可

  • 详解Spring Cloud Gateway基于服务发现的默认路由规则

    1.Spring Gateway概述 1.1 什么是Spring Cloud Gateway Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式.Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由

随机推荐