深入学习spring cloud gateway 限流熔断( 二 )


  • x-ratelimit-burst-capacity:最大令牌值,
  • x-ratelimit-replenish-rate:填充的速率值,
  • x-ratelimit-remaining:剩下可请求数 。
yaml配置:server:port: ${PORT:8085}spring:application:name: gateway-serviceredis:host: localhostport: 6379cloud:gateway:routes:- id: account-serviceuri: http://localhost:8091predicates:- Path=/account/**filters:- RewritePath=/account/(?.*), /${path}- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 10redis-rate-limiter.burstCapacity: 20
  1. Redis限流器实现
关键源码如下:
// routeId也就是我们的服务路由id,id就是限流的keypublic 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)));}
  1. 测试Redis限流器
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)@RunWith(SpringRunner.class)public class GatewayRateLimiterTest {private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);@Rulepublic TestRule benchmarkRun = new BenchmarkRule();@ClassRulepublic static MockServerContainer mockServer = new MockServerContainer();@ClassRulepublic static GenericContainer redis = new GenericContainer("redis:5.0.6").withExposedPorts(6379);@AutowiredTestRestTemplate template;@BeforeClasspublic 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());}}


推荐阅读