SpringBoot 自定义注解实现限流
SpringBoot 自定义注解实现限流
限流是为了防止服务器资源的过度消耗,通过一定的策略来控制访问频率,确保服务的高可用性和稳定性。其核心意义在于防止流量高峰时期接口过载,从而引起服务崩溃或响应延迟增加。本文将简述如何通过AOP和自定义注解实现限流。
1.创建自定义注解
可以根据需要定义更多方法,在方法上引用注解需要指定具体的值
@Target(ElementType.METHOD) //注解的目标是方法上
@Retention(RetentionPolicy.RUNTIME) //运行时
@Documented
public @interface RateLimit {String key() default "";int time() default 1; //时间,单位秒,默认1秒int count() default 100; // 允许的请求次数 ,此处为1秒100次
}
2.定义限流切面
使用AOP切面来控制方法的请求次数,如超过注解定义的请求次数限制,将抛出异常
import com.google.common.util.concurrent.RateLimiter;import com.lenovo.ssc.logistics.system.RateLimit;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Component
@Aspect
public class RateLimitAspect {private final Map<String ,RateLimiter> limiterMap=new ConcurrentHashMap<>();@Before("@annotation(rateLimit)")public void doBefore(JoinPoint point, RateLimit rateLimit){MethodSignature signature= (MethodSignature)point.getSignature();//获取方法签名Method method = signature.getMethod();//获取当前方法String name = method.getName();//获取方法名double permitsSecond=(double)rateLimit.count()/ rateLimit.time();//计算每秒允许的请求数//computeIfAbsent 方法会检查 limiterMap 中是否存在键 name。如果键 name 不存在,则使用提供的 lambda 表达式 k -> RateLimiter.create(rateLimit.count()) 创建一个新的值,并将其与键 name 关联com.google.common.util.concurrent.RateLimiter rateLimiter = limiterMap.computeIfAbsent(name, k -> com.google.common.util.concurrent.RateLimiter.create(permitsSecond));if(!rateLimiter.tryAcquire()){//ateLimiter.tryAcquire() 方法用于尝试从 Guava 的 RateLimiter 限流器中获取一个许可(token)。如果能够成功获取许可,则返回 true,否则返回 falsethrow new RuntimeException("Too many requests");}}}
3.使用注解
在Controller中使用@RateLimit注解,time用于指定时间内,count为请求次数
@GetMapping("/queryUsageMap")@Operation(summary = "获取用途map")@RateLimit(key="queryUsageMap",time = 60,count = 10)public Message<List<Map<String, String>>> queryUsageMap(@RequestParam String language) {List<Map<String, String>> usageList = SealUsageEnum.getUsageList();if (!StringUtils.isEmpty(language)) {if ("en-US".equals(language)) {usageList = SealUsageEnum.getUsageListInEg();}}return new Message<>(ResultCodeEnum.SUCCESS.getCode().toString(), ResultCodeEnum.SUCCESS.getMessage(), usageList);}
4.测试效果
在方法上增加注解,规定时间内,正常访问;超出频率报错如下:
java.lang.RuntimeException: Too many requestsat com.lenovo.ssc.logistics.system.aspect.RateLimitAspect.doBefore(RateLimitAspect.java:36)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:568)at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:617)at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44)at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:707)at com.lenovo.ssc.logistics.seal.controller.SealBasicController$$EnhancerBySpringCGLIB$$cfa31bbb.queryUsageMap(<generated>)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:568)at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)at javax.servlet.http.HttpServlet.service(HttpServlet.java:529)at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
注意:未使用该注解的方法,将不受限制
以上是使用自定义注解和AOP完成限流的所有内容,更多的限流方式如Bucket4j、Spring Cloud gateway 以及限流组件Sentinel等,关注博主后续。如有不当之处,可以在评论区评论。