1. 简介

Spring Cloud Netflix Zuul 是对 Netflix Zuul 的封装,为 Spring Boot 应用提供了 API 网关能力。虽然它提供了许多实用的功能,但默认并不包含请求限流(Rate Limiting)机制

好消息是:有一个社区开源项目 Spring Cloud Zuul RateLimit 可以无缝集成进来,帮你轻松搞定限流功能。

本文将带你快速上手这个限流组件,从依赖配置到策略定义,再到自定义扩展,让你在微服务架构中稳如老狗。


2. Maven 依赖配置

除了引入标准的 spring-cloud-starter-netflix-zuul 外,还需要添加限流插件的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

⚠️ 注意:版本号建议根据你当前使用的 Spring Cloud 版本进行匹配,避免踩不必要的坑。


3. 示例 Controller

先写个简单的 Controller 来模拟两个接口:

@Controller
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping("/simple")
    public ResponseEntity<String> getSimple() {
        return ResponseEntity.ok("Hi!");
    }

    @GetMapping("/advanced")
    public ResponseEntity<String> getAdvanced() {
        return ResponseEntity.ok("Hello, how you doing?");
    }
}

💡 这里没有做任何限流相关的代码逻辑,因为我们要把限流规则配置在 application.yml 文件中,保持业务代码干净解耦。


4. Zuul 配置属性

接下来,在 application.yml 中添加如下配置:

zuul:
  routes:
    serviceSimple:
      path: /greeting/simple
      url: forward:/
    serviceAdvanced:
      path: /greeting/advanced
      url: forward:/
  ratelimit:
    enabled: true
    repository: JPA
    policy-list:
      serviceSimple:
        - limit: 5
          refresh-interval: 60
          type:
            - origin
      serviceAdvanced:
        - limit: 1
          refresh-interval: 2
          type:
            - origin
  strip-prefix: true

配置说明:

  • zuul.routes: 定义路由映射。
  • zuul.ratelimit.policy-list: 为每个服务设置限流策略。
    • limit: 在刷新周期内允许的最大请求数。
    • refresh-interval: 刷新时间窗口(秒)。
    • type: 限流维度,支持:
      • origin: 按客户端 IP 限流 ✅
      • url: 按请求路径限流
      • user: 按用户身份限流
      • 不填则为全局服务级别限流

📌 上述配置表示:

  • /greeting/simple 接口每 60 秒最多调用 5 次;
  • /greeting/advanced 每 2 秒只能调用 1 次;

5. 测试限流效果

5.1 请求未超出限流阈值

@Test
public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {
    ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceSimple_127.0.0.1";

    assertEquals("5", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("4", headers.getFirst(HEADER_REMAINING + key));
    assertThat(
      parseInt(headers.getFirst(HEADER_RESET + key)),
      is(both(greaterThanOrEqualTo(0)).and(lessThanOrEqualTo(60000)))
    );
}

正常情况下返回 HTTP 200,并且响应头中包含限流信息:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 4
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 60000

✅ 每次请求后剩余次数递减,重置时间也在变化。


5.2 请求超出限流阈值

@Test
public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {
    ResponseEntity<String> response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
    
    for (int i = 0; i < 2; i++) {
        response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    }

    assertEquals(TOO_MANY_REQUESTS, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceAdvanced_127.0.0.1";

    assertEquals("1", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("0", headers.getFirst(HEADER_REMAINING + key));
    assertNotEquals("2000", headers.getFirst(HEADER_RESET + key));

    TimeUnit.SECONDS.sleep(2);

    response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
}

⚠️ 第二次请求会直接被拦截,返回状态码 429 Too Many Requests

此时响应头如下:

X-RateLimit-Limit-rate-limit-application_serviceAdvanced_127.0.0.1: 1
X-RateLimit-Remaining-rate-limit-application_serviceAdvanced_127.0.0.1: 0
X-RateLimit-Reset-rate-limit-application_serviceAdvanced_127.0.0.1: 268

等刷新时间过去后,再次请求恢复正常。


6. 自定义 Key 生成器

如果你觉得默认的限流 Key 不够灵活,可以自定义一个 RateLimitKeyGenerator 实现类:

@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties, 
  RateLimitUtils rateLimitUtils) {
    return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
        @Override
        public String key(HttpServletRequest request, Route route, 
          RateLimitProperties.Policy policy) {
            return super.key(request, route, policy) + "_" + request.getMethod();
        }
    };
}

这样生成的 Key 会加上请求方法名,例如:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5

📌 Spring Cloud Zuul RateLimit 会自动识别并使用该 Bean。


7. 自定义错误处理

默认使用的是 DefaultRateLimiterErrorHandler,只记录日志。如果你想自己处理存储或获取限流数据时的异常,可以这样扩展:

@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
    return new DefaultRateLimiterErrorHandler() {
        @Override
        public void handleSaveError(String key, Exception e) {
            // 自定义保存错误处理逻辑
        }

        @Override
        public void handleFetchError(String key, Exception e) {
            // 自定义读取错误处理逻辑
        }

        @Override
        public void handleError(String msg, Exception e) {
            // 全局错误处理
        }
    };
}

和 KeyGenerator 一样,这个 Bean 也会被自动加载生效。


8. 总结

通过本文我们了解了如何在 Spring Cloud Netflix Zuul 网关中集成限流功能,包括:

  • ✅ 引入限流依赖
  • ✅ 配置限流策略
  • ✅ 测试限流行为
  • ✅ 自定义 Key 和异常处理逻辑

这在高并发场景下是非常实用的安全防护手段,特别是在微服务网关层做统一控制。

📦 示例代码可参考 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-cloud-modules/spring-cloud-zuul


💡 提示:虽然 Zuul 已经进入维护阶段,但在很多企业级系统中仍广泛使用,尤其是结合限流、鉴权、监控等功能时依然强大。如果新项目推荐考虑 Spring Cloud Gateway。


原始标题:Rate Limiting in Spring Cloud Netflix Zuul