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
相关产品推荐
相关产品推荐

