深入學(xué)習(xí)spring cloud gateway 限流熔斷
目前,Spring Cloud Gateway是僅次于Spring Cloud Netflix的第二個(gè)最受歡迎的Spring Cloud項(xiàng)目(就GitHub上的星級(jí)而言)。它是作為Spring Cloud系列中Zuul代理的繼任者而創(chuàng)建的。該項(xiàng)目提供了用于微服務(wù)體系結(jié)構(gòu)的API網(wǎng)關(guān),并基于反應(yīng)式Netty和Project Reactor構(gòu)建。它旨在提供一種簡單而有效的方法來路由到API并解決諸如安全性,監(jiān)視/度量和彈性之類的普遍關(guān)注的問題。
基于Redis限流Spring Cloud Gateway為您提供了許多功能和配置選項(xiàng)。今天,我將集中討論網(wǎng)關(guān)配置的一個(gè)非常有趣的方面-速率限制。速率限制器可以定義為一種控制網(wǎng)絡(luò)上發(fā)送或接收的流量速率的方法。我們還可以定義幾種類型的速率限制。Spring Cloud Gateway當(dāng)前提供了一個(gè)Request Rate Limiter,它負(fù)責(zé)將每個(gè)用戶每秒限制為N個(gè)請(qǐng)求。與Spring Cloud Gateway一起 使用時(shí)RequestRateLimiter,我們可能會(huì)利用Redis。Spring Cloud實(shí)現(xiàn)使用令牌桶算法做限速。該算法具有集中式存儲(chǔ)桶主機(jī),您可以在其中對(duì)每個(gè)請(qǐng)求獲取令牌,然后將更多的令牌緩慢滴入存儲(chǔ)桶中。如果存儲(chǔ)桶為空,則拒絕該請(qǐng)求。
項(xiàng)目演示源碼地址: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默認(rèn)請(qǐng)求限流GatewayFilter(org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter)。使用默認(rèn)的Redis限流器方案,你可以通過自定義keyResolver類去決定Redis限流key的生成,下面舉常用幾個(gè)例子:
根據(jù)用戶: 使用這種方式限流,請(qǐng)求路徑中必須攜帶userId參數(shù)
@BeanKeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst('userId'));}
根據(jù)獲uri
@BeanKeyResolver apiKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value());}
由于我們已經(jīng)討論了Spring Cloud Gateway速率限制的一些理論方面,因此我們可以繼續(xù)進(jìn)行實(shí)施。首先,讓我們定義主類和非常簡單的KeyResolverbean,它始終等于一個(gè)。
@SpringBootApplicationpublic class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } @Bean KeyResolver userKeyResolver() { return exchange -> Mono.just('1'); }}
Gateway默認(rèn)使用org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter限流器。 現(xiàn)在,如通過模擬Http請(qǐng)求,則會(huì)收到以下響應(yīng)。它包括一些特定的header,其前綴為x-ratelimit。
x-ratelimit-burst-capacity:最大令牌值, x-ratelimit-replenish-rate:填充的速率值, x-ratelimit-remaining:剩下可請(qǐng)求數(shù)。yaml配置:
server: port: ${PORT:8085}spring: application: name: gateway-service redis: host: localhost port: 6379 cloud: gateway: routes: - id: account-serviceuri: http://localhost:8091predicates:- Path=/account/**filters:- RewritePath=/account/(?.*), /${path} - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20Redis限流器實(shí)現(xiàn)
關(guān)鍵源碼如下:
// routeId也就是我們的服務(wù)路由id,id就是限流的keypublic Mono<Response> isAllowed(String routeId, String id) { // 會(huì)判斷RedisRateLimiter是否初始化了 if (!this.initialized.get()) { throw new IllegalStateException('RedisRateLimiter is not initialized'); } // 獲取routeId對(duì)應(yīng)的限流配置 Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig); if (routeConfig == null) { throw new IllegalArgumentException('No Configuration found for route ' + routeId); } // 允許用戶每秒做多少次請(qǐng)求 int replenishRate = routeConfig.getReplenishRate(); // 令牌桶的容量,允許在一秒鐘內(nèi)完成的最大請(qǐng)求數(shù) 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) // 執(zhí)行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()); }}
執(zhí)行Test類: 發(fā)現(xiàn)超過20之后會(huì)被攔截返回429,運(yùn)行過程中隨著令牌的放入會(huì)不斷有請(qǐng)求成功。
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]如果默認(rèn)的限流器不能夠滿足使用,可以通過繼承AbstractRateLimiter實(shí)現(xiàn)自定義限流器,然后通過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 斷路器介紹
三個(gè)一般性狀態(tài)
CLOSED:關(guān)閉狀態(tài),放過所有請(qǐng)求,記錄請(qǐng)求狀態(tài)。 OPEN:打開,異常請(qǐng)求達(dá)到閥值數(shù)量時(shí),開啟熔斷,拒絕所有請(qǐng)求。 HALF_OPEN:半開,放開一定數(shù)量的請(qǐng)求,重新計(jì)算錯(cuò)誤率。兩個(gè)特定狀態(tài)
DISABLED:禁用 FORCED_OPEN:強(qiáng)開狀態(tài)之間轉(zhuǎn)換
啟動(dòng)時(shí)斷路器為CLOSE狀態(tài),在達(dá)到一定請(qǐng)求量至后計(jì)算請(qǐng)求失敗率,達(dá)到或高于指定失敗率后,斷路進(jìn)入open狀態(tài),阻攔所有請(qǐng)求,開啟一段時(shí)間(自定義)時(shí)間后,斷路器變?yōu)閔alfOpen狀態(tài),重新計(jì)算請(qǐng)求失敗率。halfOpen錯(cuò)誤率低于指定失敗率后,斷路進(jìn)入close狀態(tài),否則進(jìn)入open狀態(tài)。
狀態(tài)轉(zhuǎn)換
通過Resilience4J啟用Spring Cloud Gateway斷路器要啟用構(gòu)建在Resilience4J之上的斷路器,我們需要聲明一個(gè)Customizer傳遞了的bean ReactiveResilience4JCircuitBreakerFactory。可以非常簡單地去配置設(shè)置,下面使用默認(rèn)配置進(jìn)行測試
@Beanpublic Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .circuitBreakerConfig(CircuitBreakerConfig.custom() //統(tǒng)計(jì)失敗率的請(qǐng)求總數(shù) .slidingWindowSize(5) //在半開狀態(tài)下請(qǐng)求的次數(shù) .permittedNumberOfCallsInHalfOpenState(5) //斷路器打開的成功率 .failureRateThreshold(50.0F) //斷路器打開的周期 .waitDurationInOpenState(Duration.ofMillis(30)) //屬于慢請(qǐng)求的周期 .slowCallDurationThreshold(Duration.ofMillis(200))//慢請(qǐng)求打開斷路器的成功率 .slowCallRateThreshold(50.0F) .build()) .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build()).build());}測試Resilience4J斷路器
使用默認(rèn)配置進(jìn)行測試
@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()); }
執(zhí)行下面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); }}
請(qǐng)求日志如下:當(dāng)請(qǐng)求達(dá)到100次時(shí)候,此時(shí)失敗率為50% 這時(shí)候系統(tǒng)開啟斷路器返回503!
20:07:29.281 --- [pool-2-thread-1] : 91. Received: status->200, payload->Account(id=1, number=1234567890), call->120:07:30.297 --- [pool-2-thread-1] : 92. Received: status->504, payload->Account(id=null, number=null), call->220:07:30.316 --- [pool-2-thread-1] : 93. Received: status->200, payload->Account(id=1, number=1234567890), call->120:07:31.328 --- [pool-2-thread-1] : 94. Received: status->504, payload->Account(id=null, number=null), call->220:07:31.345 --- [pool-2-thread-1] : 95. Received: status->200, payload->Account(id=1, number=1234567890), call->120:07:32.359 --- [pool-2-thread-1] : 96. Received: status->504, payload->Account(id=null, number=null), call->220:07:32.385 --- [pool-2-thread-1] : 97. Received: status->200, payload->Account(id=1, number=1234567890), call->120:07:33.400 --- [pool-2-thread-1] : 98. Received: status->504, payload->Account(id=null, number=null), call->220:07:33.414 --- [pool-2-thread-1] : 99. Received: status->200, payload->Account(id=1, number=1234567890), call->120:07:34.509 --- [pool-2-thread-1] : 100. Received: status->504, payload->Account(id=null, number=null), call->220:07:34.525 --- [pool-2-thread-1] : 101. Received: status->503, payload->Account(id=null, number=null), call->120:07:34.533 --- [pool-2-thread-1] : 102. Received: status->503, payload->Account(id=null, number=null), call->220:07:34.539 --- [pool-2-thread-1] : 103. Received: status->503, payload->Account(id=null, number=null), call->120:07:34.545 --- [pool-2-thread-1] : 104. Received: status->503, payload->Account(id=null, number=null), call->220:07:34.552 --- [pool-2-thread-1] : 105. Received: status->503, payload->Account(id=null, number=null), call->120:07:34.566 --- [pool-2-thread-1] : 106. Received: status->503, payload->Account(id=null, number=null), call->220:07:34.572 --- [pool-2-thread-1] : 107. Received: status->503, payload->Account(id=null, number=null), call->120:07:34.576 --- [pool-2-thread-1] : 108. Received: status->503, payload->Account(id=null, number=null), call->220:07:34.580 --- [pool-2-thread-1] : 109. Received: status->503, payload->Account(id=null, number=null), call->120:07:34.586 --- [pool-2-thread-1] : 110. Received: status->503, payload->Account(id=null, number=null), call->220:07:34.591 --- [pool-2-thread-1] : 111. Received: status->503, payload->Account(id=null, number=null), call->1
這時(shí)候我們修改下配置
@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')); }
新建一個(gè)回調(diào)接口,用于斷路器打開后請(qǐng)求的地址。
@RestController@RequestMapping('/fallback')public class GatewayFallback { @GetMapping('/account') public Account getAccount() {Account a = new Account();a.setId(2);a.setNumber('123456');return a; }}
使用默認(rèn)設(shè)置時(shí),前3次請(qǐng)求觸發(fā)斷路器回調(diào),后面正常請(qǐng)求成功
20:20:23.529 --- [pool-2-thread-1] : 1. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:23.777 --- [pool-2-thread-1] : 2. Received: status->200, payload->Account(id=2, number=123456), call->220:20:23.808 --- [pool-2-thread-1] : 3. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:24.018 --- [pool-2-thread-1] : 4. Received: status->200, payload->Account(id=2, number=123456), call->220:20:24.052 --- [pool-2-thread-1] : 5. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:24.268 --- [pool-2-thread-1] : 6. Received: status->200, payload->Account(id=2, number=123456), call->220:20:24.301 --- [pool-2-thread-1] : 7. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:24.317 --- [pool-2-thread-1] : 8. Received: status->200, payload->Account(id=2, number=1234567891), call->220:20:24.346 --- [pool-2-thread-1] : 9. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:24.363 --- [pool-2-thread-1] : 10. Received: status->200, payload->Account(id=2, number=1234567891), call->220:20:24.378 --- [pool-2-thread-1] : 11. Received: status->200, payload->Account(id=1, number=1234567890), call->120:20:24.392 --- [pool-2-thread-1] : 12. Received: status->200, payload->Account(id=2, number=1234567891), call->220:20:24.402 --- [pool-2-thread-1] : 13. Received: status->200, payload->Account(id=1, number=1234567890), call->1
至此給大家介紹了Spring Cloud Gateway中斷路器跟限流器使用。更多相關(guān)spring cloud gateway 限流熔斷內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
