xkcoding/spring-boot-demo · warning · RuntimeException
手速太快了,慢点儿吧~
Error message
手速太快了,慢点儿吧~
What it means
Thrown by the Guava RateLimiterAspect when a method annotated with @RateLimiter(qps=N) receives requests faster than the configured QPS allows. The aspect uses a per-method-name Guava RateLimiter token-bucket; if tryAcquire(timeout, timeUnit) returns false within the wait window, a RuntimeException is thrown. The cache key is method.getName() only, so overloaded methods with the same name share a single limiter.
Source
Thrown at demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java:52
}
@Around("rateLimit()")
public Object pointcut(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解
RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);
if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) {
double qps = rateLimiter.qps();
if (RATE_LIMITER_CACHE.get(method.getName()) == null) {
// 初始化 QPS
RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps));
}
log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());
// 尝试获取令牌
if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) {
throw new RuntimeException("手速太快了,慢点儿吧~");
}
}
return point.proceed();
}
}
View on GitHub (pinned to 87a142f960)
Solutions
- Increase the qps value on the @RateLimiter annotation to match expected throughput.
- Set a non-zero timeout to allow short bursts to wait for a token rather than failing immediately.
- Catch the RuntimeException in a global @ControllerAdvice and return a 429 Too Many Requests response.
- Use the method signature (not just name) as the cache key to avoid collisions between overloaded methods.
Example fix
// before — RuntimeException, no HTTP semantics
throw new RuntimeException("手速太快了,慢点儿吧~");
// after — throw a custom exception the handler maps to 429
throw new RateLimitException("请求过于频繁,请稍后重试");
// And in cache key fix:
// before
RATE_LIMITER_CACHE.put(method.getName(), ...)
// after
RATE_LIMITER_CACHE.put(method.getDeclaringClass().getName() + "#" + method.getName(), ...) Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling a rate-limited method, consider client-side throttling // No server-side pre-check exists; the limiter is enforced via AOP. // On the client, implement exponential backoff or a request queue.
Try / catch
// In a @ControllerAdvice handler — map RuntimeException from the aspect to HTTP 429
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public ResponseEntity<?> handleRateLimit(RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("手速太快了")) {
return ResponseEntity.status(429).body(ApiResponse.ofMessage(429, "Too Many Requests"));
}
throw e;
} Prevention
- Tune @RateLimiter(qps=N) to match production traffic expectations.
- Set a non-zero timeout on the annotation so short bursts wait instead of failing.
- Use the method signature (class + name) as the cache key to avoid overloaded-method collisions.
- Create a custom RateLimitException instead of RuntimeException for clean HTTP 429 mapping.
When it happens
Trigger: A client sends requests to a @RateLimiter-annotated method at a rate exceeding the configured qps value. After the token bucket depletes and the timeout (default 0ms) elapses, tryAcquire returns false and the exception fires.
Common situations: Burst traffic to a rate-limited API endpoint; load testing without accounting for QPS limits; QPS set too low for production traffic; two overloaded methods sharing a name competing for the same bucket.
Related errors
AI-assisted analysis of xkcoding/spring-boot-demo@87a142f960 (2026-08-14).
Data as JSON: /api/errors/1250b9f62db1126b.
Report an issue: GitHub.