@SentinelResource不能用于全局限流,因其是方法粒度的,需每个接口手动添加、配置value和blockHandler,维护成本高,且无法统一管控路径级或路由级规则(如所有/api/v1/order/**共用QPS=50)。

为什么不能直接用 @SentinelResource 做全局限流
因为 @SentinelResource 是方法粒度的,每个接口都要手动加、配 value、写 blockHandler,一旦 Controller 层有几十个方法,维护成本爆炸。更关键的是:它无法统一管控「路径级」或「路由级」规则(比如所有 /api/v1/order/** 接口共用一套 QPS=50 的流控),而生产环境真正需要的是这种语义清晰、可批量下发的策略。
自定义注解必须绕开的三个硬限制
Spring AOP 无法拦截 @RestController 中的 @GetMapping 等注解方法(它们被 RequestMappingHandlerAdapter 处理),所以自定义注解不能只靠普通切面。必须满足:
- 切点得落在 Spring MVC 的 HandlerExecutionChain 链路中,推荐在
HandlerInterceptor.preHandle或自定义WebMvcConfigurer注入的HandlerMethodArgumentResolver后置处理 - 资源名必须能稳定生成,例如用
request.getRequestURL().toString()+request.getMethod()拼接,避免用反射取方法名(Controller 可能被代理、重命名) -
SphU.entry()必须配对entry.exit(),且不能在异步线程里漏调;若用@Async或CompletableFuture,需改用SphU.asyncEntry()
一个可用的生产级自定义注解模板
定义注解:
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface RateLimit { String value() default ""; // 资源标识前缀 int qps() default 10; // 默认阈值 String fallback() default ""; // 方法级降级方法名}
实际生效靠拦截器:
public class SentinelRateLimitInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) return true; HandlerMethod hm = (HandlerMethod) handler; RateLimit anno = hm.getMethodAnnotation(RateLimit.class); if (anno == null) anno = hm.getBeanType().getAnnotation(RateLimit.class); if (anno == null) return true; String resourceName = StringUtils.hasText(anno.value()) ? anno.value() : request.getMethod() + ":" + request.getRequestURI(); try (Entry entry = SphU.entry(resourceName, EntryType.IN, 1, request.getParameterMap())) { return true; } catch (BlockException e) { response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.getWriter().write("{\"code\":429,\"msg\":\"rate limited\"}"); return false; } }}
注册进 WebMvc:
@Configurationpublic class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SentinelRateLimitInterceptor()) .excludePathPatterns("/actuator/**", "/swagger-ui/**"); }}
fallback 方法不生效?检查这三点
@SentinelResource 的 blockHandler 或自定义注解里的降级逻辑失效,常见于:
-
blockHandler方法签名不匹配:必须和原方法**参数完全一致**,且末尾多加一个BlockException参数;返回值类型也必须相同 - 方法不是 public:
blockHandler方法必须是 public,否则 Sentinel 反射调用失败,直接抛NoSuchMethodException - 没开 AOP 支持:主类缺少
@EnableAspectJAutoProxy,或没声明@Bean public SentinelResourceAspect sentinelResourceAspect()
真正难缠的是跨服务调用链路中的 fallback 透传——Sentinel 默认不传播 block 异常,下游服务收到的是原始 HTTP 500,而不是你定义的友好提示。这时候得配合 Feign 的 fallbackFactory 或 RestTemplate 的 ResponseErrorHandler 手动补全。










