拦截器实现接口限流

文章介绍了如何在Spring框架中实现接口限流,包括通过拦截器在请求早期进行限流判断,使用RateLimiter创建令牌桶进行限流,以及自定义注解配合AOP进行方法级别的限流。这种方式提高了系统的稳定性和可配置性。
摘要由CSDN通过智能技术生成

背景

原本有在公司框架层的preservice写限流,但经历线上流量突增的雪崩后排查发现,有些接口有非常耗时的参数解密算法在限流之前,这也是个风险点。所以提出了把接口限流方法放到spring拦截器里,在请求一进来的阶段,就先进行限流判断。

实现方式

1、限流方法

首先是限流方法,通过RateLimiter实现,这个网上有很多教程可以参考。

首先创建令牌桶(每秒允许10个请求的限流器对象):

RateLimiter limiter =RateLimiter.create(10);

多渠道限流中可以构建个Map来按业务划分限流值(根据传入的scene取不同的限流值):

private final Map<String,RateLimiter> sceneLimitMap = new ConcurrentHashMap<>();

使用的核心方法(false达到限流值):

boolean permit = RateLimiter.get(method).tryAcquire(tryAcquireTimeOut, TimeUnit.MILLISECONDS)
boolean permit = sceneLimitMap.get(scene).tryAcquire(tryAcquireTimeOut, TimeUnit.MILLISECONDS)

2、限流拦截器

首先创建类实现拦截器方法:

public class TryAcquireInterceptor implements AsyncHandlerInterceptor 

重写处理方法:

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 

其中可以通过下面这些方法得到当前请求的uri和方法名,以此判断是否是需要限流的方法和接口,做成可配置的限流方式:

if(handler instanceof HandlerMethod){
String requestUri = request.getRequestURI();
methodName = handler.getMethod().getDeclaringClass().getName()
}

如果是需要限流的方法,就可以在下一步调用 1、限流方法

3、加入拦截器

在@Configuration的类里添加拦截器:

    //添加限流拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] addPath = {"/**"};
        String[] exludePath = {"/error"};

        registry.addInterceptor(tryAcquireInterceptor)
                .addPathPatterns(addPath)
                .excludePathPatterns(exludePath)
        ;
    }

另外还看到了一种初始化方法可以使用的注解:

@ PostConstruct
Java自带的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。

4、切面注解实现限流

(1)自定义限流注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Limit {
    /**
     * 资源的key,唯一
     * 作用:不同的接口,不同的流量控制
     */
    String key() default "";
 
    /**
     * 最多的访问限制次数
     */
    double permitsPerSecond () ;
 
    /**
     * 获取令牌最大等待时间
     */
    long timeout();
 
    /**
     * 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
     */
    TimeUnit timeunit() default TimeUnit.MILLISECONDS;
 
    /**
     * 得不到令牌的提示语
     */
    String msg() default "系统繁忙,请稍后再试.";
}

(2)使用AOP切面拦截限流注解

@Slf4j
@Aspect
@Component
public class LimitAop {
    /**
     * 不同的接口,不同的流量控制
     * map的key为 Limiter.key
     */
    private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
 
    @Around("@annotation(com.jianzh5.blog.limit.Limit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //拿limit的注解
        Limit limit = method.getAnnotation(Limit.class);
        if (limit != null) {
            //key作用:不同的接口,不同的流量控制
            String key=limit.key();
            RateLimiter rateLimiter = null;
            //验证缓存是否有命中key
            if (!limitMap.containsKey(key)) {
                // 创建令牌桶
                rateLimiter = RateLimiter.create(limit.permitsPerSecond());
                limitMap.put(key, rateLimiter);
                log.info("新建了令牌桶={},容量={}",key,limit.permitsPerSecond());
            }
            rateLimiter = limitMap.get(key);
            // 拿令牌
            boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
            // 拿不到命令,直接返回异常提示
            if (!acquire) {
                log.debug("令牌桶={},获取令牌失败",key);
                this.responseFail(limit.msg());
                return null;
            }
        }
        return joinPoint.proceed();
    }
 
    /**
     * 直接向前端抛出异常
     * @param msg 提示信息
     */
    private void responseFail(String msg)  {
        HttpServletResponse response=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        ResultData<Object> resultData = ResultData.fail(ReturnCode.LIMIT_ERROR.getCode(), msg);
        WebUtils.writeJson(response,resultData);
    }
}

(3)给需要限流的接口加上注解

@Slf4j
@RestController
@RequestMapping("/limit")
public class LimitController {
    
    @GetMapping("/test2")
    @Limit(key = "limit2", permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS,msg = "当前排队人数较多,请稍后再试!")
    public String limit2() {
        log.info("令牌桶limit2获取令牌成功");
        return "ok";
    }
 
 
    @GetMapping("/test3")
    @Limit(key = "limit3", permitsPerSecond = 2, timeout = 500, timeunit = TimeUnit.MILLISECONDS,msg = "系统繁忙,请稍后再试!")
    public String limit3() {
        log.info("令牌桶limit3获取令牌成功");
        return "ok";
    }
}

总结

通过以上几步,就可以简单实现拦截器限流的方式。之后如果需要在服务接口响应之前做些公共操作也可以考虑拦截器的方式。方便且容易实现,步骤很清晰,可以专注于功能本身的开发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中,可以通过自定义注解和AOP实现接口限流。具体实现步骤如下: 1. 定义注解 自定义一个注解,用于标记需要进行限流方法。例如: ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { int value() default 10; // 默认每秒最多处理10个请求 } ``` 2. 实现拦截器 定义一个切面拦截器,拦截被RateLimit注解标记的方法,并根据注解中定义的限流参数进行限流。例如: ``` @Aspect @Component public class RateLimitInterceptor { private final Map<String, Integer> counterMap = new ConcurrentHashMap<>(); @Pointcut("@annotation(com.example.demo.annotation.RateLimit)") public void rateLimit() {} @Around("rateLimit()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); RateLimit rateLimit = method.getAnnotation(RateLimit.class); String methodName = method.getName(); Integer counter = counterMap.get(methodName); if (counter == null) { counter = 0; } if (counter >= rateLimit.value()) { throw new RuntimeException("接口访问频率过高"); } counterMap.put(methodName, counter + 1); try { return joinPoint.proceed(); } finally { counterMap.put(methodName, counter); } } } ``` 3. 配置拦截器 将切面拦截器注册到Spring容器中,并配置AOP自动代理。 ``` @Configuration @EnableAspectJAutoProxy public class RateLimitConfig { @Bean public RateLimitInterceptor rateLimitInterceptor() { return new RateLimitInterceptor(); } } ``` 4. 使用注解 在需要进行限流方法上添加RateLimit注解,并指定限流参数。例如: ``` @RestController public class UserController { @RateLimit(5) @GetMapping("/users") public List<User> getUsers() { // ... } } ``` 以上就是在Spring Boot中自己实现接口限流的基本步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值