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

Resilience4j RateLimiter异常:同时触发RequestNotPermitted与429错误

问题解答

疑问1:为何场景2同时出现本地限流异常与远程API的429错误?

这是因为你使用的Resilience4j 1.7.0的RateLimiter默认实现(SemaphoreRateLimiter)的特性导致的:

  • 它基于许可池机制工作:每个刷新周期(1秒)会补充6个许可,但如果请求处理完成后快速释放许可,这些许可会被新的请求立即复用。
  • 场景2中15线程在1秒内逐步启动,当第一批6个请求快速处理完成(比如OAuth2 token已缓存、Feign请求耗时短),许可被释放,后续请求会拿到新的许可并发送到远程API,导致远程API在1秒内收到超过6个请求,触发其自身的限流规则返回429。
  • 同时,仍有部分请求因无法获取许可被本地限流拦截,抛出RequestNotPermitted异常。

而场景1是8线程瞬间启动,所有请求同时竞争6个许可,2个请求直接被拦截,剩下6个请求同时处理,即使处理完成,剩余时间内没有新的请求(因为JMeter循环1次),所以远程API只会收到6个请求,不会触发429。

疑问2:为何RateLimiter仅在一定并发量下有效,超过后允许超过6请求/秒的调用?

核心原因还是SemaphoreRateLimiter的设计逻辑:
它的limitForPeriod参数是每个刷新周期补充的许可数量,而非严格限制每个周期内的最大请求数。如果请求处理速度足够快(比如耗时远小于1/6秒),许可会被快速释放并复用,导致一个刷新周期(1秒)内实际处理的请求数超过6个。

你的单元测试之所以正常,是因为测试中请求是批量提交后同步等待,请求处理过程中许可不会被提前释放,所以严格控制了每个周期的请求数;但实际压测中请求是逐步到达且处理快速,许可复用导致了超量请求。

解决方案

要实现严格的每秒6请求的速率限制,适配远程API的规则,可以采用以下方案:

1. 切换到Resilience4j 2.x版本的RateLimiter

Resilience4j 2.x重构了RateLimiter实现,新增了基于滑动窗口的限流策略,能够严格控制单位时间内的请求数。升级后配置如下:

resilience4j:
  ratelimiter:
    instances:
      user-throttling:
        limit-for-period: 6
        limit-refresh-period: 1s
        timeout-duration: 0
        # 指定使用滑动窗口实现
        strategy: SLIDING_WINDOW

2. 在1.x版本中结合Bulkhead控制并发

如果无法升级版本,可以用Resilience4j的Bulkhead(舱壁)限制并发请求数,结合RateLimiter来间接控制速率:

  • 配置Bulkhead限制最大并发数为6,这样同一时间最多6个请求在处理。
  • 若请求平均处理时间超过167ms(1/6秒),则1秒内最多处理6个请求,刚好匹配远程API规则。

配置示例:

resilience4j:
  bulkhead:
    instances:
      user-bulkhead:
        max-concurrent-calls: 6
  ratelimiter:
    instances:
      user-throttling:
        limitForPeriod: 6
        limitRefreshPeriod: 1s
        timeoutDuration: 0

然后在Feign接口方法上同时添加注解:

@FeignClient(...)
public interface UserClient {
    @GetMapping(value = "/users", produces = APPLICATION_JSON_VALUE)
    @RateLimiter(name = "user-throttling")
    @Bulkhead(name = "user-bulkhead")
    List<JsonNode> getUsers();
}

3. 改用Guava RateLimiter

如果Resilience4j的实现不符合需求,可以集成Guava的RateLimiter,它基于令牌桶算法,能严格控制速率:

  • 定义一个RateLimiter Bean:
@Configuration
public class RateLimiterConfig {
    @Bean("userRateLimiter")
    public RateLimiter userRateLimiter() {
        return RateLimiter.create(6.0); // 每秒6个请求
    }
}
  • 在Feign客户端的方法中通过AOP切面实现限流:
@FeignClient(...)
public interface UserClient {
    @GetMapping(value = "/users", produces = APPLICATION_JSON_VALUE)
    List<JsonNode> getUsers();
}

// 自定义切面
@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    @Qualifier("userRateLimiter")
    private RateLimiter rateLimiter;

    @Around("execution(* com.yourpackage.UserClient.getUsers(..))")
    public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        rateLimiter.acquire();
        return joinPoint.proceed();
    }
}

内容的提问来源于stack exchange,提问作者Filip Kowalski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:07:06