① pom 依赖

这里一定要注意,是网关引入的redis-reactive,背压模式的redis。

  1. <!--基于 reactive stream 的redis -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
  5. </dependency>

② 配置按照请求IP 的限流

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: requestratelimiter_route
  6. uri: lb://pig-upms
  7. order: 10000
  8. predicates:
  9. - Path=/admin/**
  10. filters:
  11. - name: RequestRateLimiter
  12. args:
  13. redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
  14. redis-rate-limiter.burstCapacity: 3 # 令牌桶总容量
  15. key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
  16. - StripPrefix=1
  • 配置bean,多维度限流量的入口 对应上边key-resolver
  1. /**
  2. * 自定义限流标志的key,多个维度可以从这里入手
  3. * exchange对象中获取服务ID、请求信息,用户信息等
  4. */
  5. @Bean
  6. KeyResolver remoteAddrKeyResolver() {
  7. return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
  8. }

OK 完成。

③ 压力测试

并发5个线程。

网关限流使用 - 图1

Redis 数据变化

我们使用redis的monitor 命令,实时查看redis 的操作情况。
会发现在redis中会操作两个key

  • request_rate_limiter.{xxx}.timestamp
  • request_rate_limiter.{xxx}.tokens

网关限流使用 - 图2

④ 实现原理

网关限流使用 - 图3

Spring Cloud Gateway 默认实现 Redis限流,如果扩展只需要实现ratelimter接口即可。

核心代码
  • 判断是否取到令牌的实现,通过调用 redis的LUA 脚本。
  1. public Mono<Response> isAllowed(String routeId, String id) {
  2. Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
  3. int replenishRate = routeConfig.getReplenishRate();
  4. int burstCapacity = routeConfig.getBurstCapacity();
  5. try {
  6. List<String> keys = getKeys(id);
  7. returns unixtime in seconds.
  8. List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
  9. Instant.now().getEpochSecond() + "", "1");
  10. // 这里是核心,执行redis 的LUA 脚本。
  11. Flux<List<Long>> flux =
  12. this.redisTemplate.execute(this.script, keys, scriptArgs);
  13. return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
  14. .reduce(new ArrayList<Long>(), (longs, l) -> {
  15. longs.addAll(l);
  16. return longs;
  17. }) .map(results -> {
  18. boolean allowed = results.get(0) == 1L;
  19. Long tokensLeft = results.get(1);
  20. Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
  21. if (log.isDebugEnabled()) {
  22. log.debug("response: " + response);
  23. }
  24. return response;
  25. });
  26. }
  27. catch (Exception e) {
  28. log.error("Error determining if user allowed from redis", e);
  29. }
  30. return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
  31. }

LUA 脚本

网关限流使用 - 图4