You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Resilience4j RateLimiter首周期请求超限问题排查与解决

问题与尝试的解决方案

当前遇到的问题是Resilience4j RateLimiter未遵循首周期配置,允许通过的请求数超出设定值。

要复现问题或使用该“解决方案”,只需注释/取消注释rateLimiter Bean中的warmup方法调用即可。

注意:该方案仅为尝试性解决,即便启用,首周期与第二周期的等待间隔有时仍略短于预期。

MyBeans代码

@Configuration
public class MyBeans {
    private static void warmup(final RateLimiter rateLimiter) {
        final int limitForPeriod = rateLimiter.getRateLimiterConfig().getLimitForPeriod();
        for (int i = 0; i < limitForPeriod; i++) {
            Mono.empty()
                    .transformDeferred(RateLimiterOperator.of(rateLimiter))
                    .block();
        }
    }

    @Bean
    public RateLimiter rateLimiter() {
        final RateLimiterConfig config = RateLimiterConfig.custom()
                .timeoutDuration(Duration.ofMinutes(15))
                .limitRefreshPeriod(Duration.ofSeconds(10))
                .limitForPeriod(5)
                .build();
        final RateLimiter rateLimiter = new AtomicRateLimiter("limiter", config);
        warmup(rateLimiter);
        return rateLimiter;
    }
}

MyClient代码

@Service
public class MyClient {

    private final RateLimiter rateLimiter;

    public MyClient(final RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    private Mono<String> getDataFrom() {
        return Mono.fromSupplier(() -> {
            final Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
            final String threadName = Thread.currentThread().getName();
            final String text = "making request...";
            System.out.printf("%s - %s: %s%n", now, threadName, text);
            return "data:" + ThreadLocalRandom.current().nextInt(100);
        });
    }

    public Mono<String> getData() {
        return getDataFrom()
            .transformDeferred(RateLimiterOperator.of(rateLimiter));
    }
}

为完整展示,以下是处理器与路由代码:

DataHandler代码

@Component
public final class DataHandler {

    private final MyClient myClient;

    public DataHandler(MyClient myClient) {
        this.myClient = myClient;
    }

    public Mono<ServerResponse> getData(final ServerRequest request) {
        final Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
        final String threadName = Thread.currentThread().getName();
        final String text = "/data called...";
        System.out.printf("%s - %s: %s%n", now, threadName, text);
        return myClient.getData()
                .flatMap(l -> ServerResponse.ok()
                        .contentType(MediaType.APPLICATION_JSON)
                        .bodyValue(l));
    }
}

DataRouter代码

@Configuration(proxyBeanMethods = false)
public class DataRouter {

    @Bean
    public RouterFunction<ServerResponse> getData(final DataHandler dataHandler) {
        final RequestPredicate request = RequestPredicates.GET("/data")
                .and(RequestPredicates.accept(MediaType.APPLICATION_JSON));

        return RouterFunctions.route(request, dataHandler::getData);
    }
}

随后通过Postman Runner或以下命令并行发送大量请求:

seq 1 80 | xargs -Iname -P12  curl "http://localhost:8088/data"

输出结果

2024-06-26T19:04:12.375Z - parallel-5: /data called...
2024-06-26T19:04:12.375Z - parallel-4: /data called...
2024-06-26T19:04:12.375Z - parallel-8: /data called...
2024-06-26T19:04:12.375Z - parallel-1: /data called...
2024-06-26T19:04:12.375Z - parallel-3: /data called...
2024-06-26T19:04:12.380Z - parallel-2: making request...
2024-06-26T19:04:12.380Z - parallel-5: making request...
2024-06-26T19:04:12.380Z - parallel-4: making request...
2024-06-26T19:04:12.380Z - parallel-6: making request...
2024-06-26T19:04:12.380Z - parallel-7: making request...
2024-06-26T19:04:12.381Z - parallel-3: /data called...
2024-06-26T19:04:12.381Z - parallel-1: /data called...
2024-06-26T19:04:12.417Z - parallel-2: /data called...
2024-06-26T19:04:12.418Z - parallel-2: /data called...
2024-06-26T19:04:12.446Z - parallel-2: /data called...
2024-06-26T19:04:12.446Z - parallel-4: /data called...
2024-06-26T19:04:12.451Z - parallel-8: /data called...
2024-06-26T19:04:12.451Z - parallel-7: /data called...
2024-06-26T19:04:12.452Z - parallel-1: /data called...
2024-06-26T19:04:19.376Z - parallel-8: making request...
2024-06-26T19:04:19.376Z - parallel-7: making request...
2024-06-26T19:04:19.377Z - parallel-6: making request...
2024-06-26T19:04:19.377Z - parallel-5: making request...
2024-06-26T19:04:19.377Z - parallel-4: making request...
2024-06-26T19:04:19.412Z - parallel-6: /data called...
2024-06-26T19:04:19.412Z - parallel-1: /data called...
2024-06-26T19:04:19.412Z - parallel-7: /data called...
2024-06-26T19:04:19.412Z - parallel-8: /data called...
2024-06-26T19:04:19.412Z - parallel-5: /data called...
2024-06-26T19:04:29.373Z - parallel-3: making request...
2024-06-26T19:04:29.374Z - parallel-1: making request...
2024-06-26T19:04:29.373Z - parallel-5: making request...
2024-06-26T19:04:29.373Z - parallel-6: making request...
2024-06-26T19:04:29.380Z - parallel-3: making request...
2024-06-26T19:04:29.405Z - parallel-7: /data called...
2024-06-26T19:04:29.405Z - parallel-8: /data called...

可见请求分别在12秒、19秒、29秒执行,而预期应在12秒、22秒、32秒左右执行。

问题咨询

  • 此现象是我的实现有误还是Resilience4j的Bug?
  • 我的临时方案有时有效有时无效,原因是什么?
  • 是否有彻底解决该问题的方法?

补充信息

以下是项目依赖配置:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

...

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- Spring boot starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- For jakarta.validation package -->
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- Rate limiter -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-ratelimiter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- Required for RateLimitOperator -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-reactor</artifactId>
            <version>2.2.0</version>
        </dependency>
...

内容的提问来源于stack exchange,提问作者Ludvig W

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:05:55