{"record":{"id":"1250b9f62db1126b","repo":"xkcoding/spring-boot-demo","slug":"error-1250b9","errorCode":null,"errorMessage":"手速太快了，慢点儿吧~","messagePattern":"手速太快了，慢点儿吧~","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java","lineNumber":52,"sourceCode":"    }\n\n    @Around(\"rateLimit()\")\n    public Object pointcut(ProceedingJoinPoint point) throws Throwable {\n        MethodSignature signature = (MethodSignature) point.getSignature();\n        Method method = signature.getMethod();\n        // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解\n        RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);\n        if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) {\n            double qps = rateLimiter.qps();\n            if (RATE_LIMITER_CACHE.get(method.getName()) == null) {\n                // 初始化 QPS\n                RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps));\n            }\n\n            log.debug(\"【{}】的QPS设置为: {}\", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());\n            // 尝试获取令牌\n            if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) {\n                throw new RuntimeException(\"手速太快了，慢点儿吧~\");\n            }\n        }\n        return point.proceed();\n    }\n}\n","sourceCodeStart":34,"sourceCodeEnd":58,"githubUrl":"https://github.com/xkcoding/spring-boot-demo/blob/87a142f9604c1a5365b4d24d22c2c11c26a9d5ab/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java#L34-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — RuntimeException, no HTTP semantics\nthrow new RuntimeException(\"手速太快了，慢点儿吧~\");\n\n// after — throw a custom exception the handler maps to 429\nthrow new RateLimitException(\"请求过于频繁，请稍后重试\");\n\n// And in cache key fix:\n// before\nRATE_LIMITER_CACHE.put(method.getName(), ...)\n// after\nRATE_LIMITER_CACHE.put(method.getDeclaringClass().getName() + \"#\" + method.getName(), ...)","handlingStrategy":"try-catch","validationCode":"// Before calling a rate-limited method, consider client-side throttling\n// No server-side pre-check exists; the limiter is enforced via AOP.\n// On the client, implement exponential backoff or a request queue.","typeGuard":null,"tryCatchPattern":"// In a @ControllerAdvice handler — map RuntimeException from the aspect to HTTP 429\n@ExceptionHandler(RuntimeException.class)\n@ResponseBody\npublic ResponseEntity<?> handleRateLimit(RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"手速太快了\")) {\n        return ResponseEntity.status(429).body(ApiResponse.ofMessage(429, \"Too Many Requests\"));\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["rate-limiting","guava","aop","token-bucket","throttling","aspectj"],"backgroundTag":null,"analyzedSha":"87a142f9604c1a5365b4d24d22c2c11c26a9d5ab","analyzedAt":"2026-08-14T01:16:58.217Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}