├── .gitignore ├── Readme.md ├── pom.xml └── src └── main ├── java └── com │ └── genxiaogu │ └── ratelimiter │ ├── advice │ ├── MethodRateLimiterAdvisor.java │ ├── MethodRateLimiterBeforeInterceptor.java │ ├── UserRateLimiterAdvisor.java │ └── UserRateLimiterBeforeInterceptor.java │ ├── annotation │ ├── Limiter.java │ └── UserLimiter.java │ ├── common │ └── LimiterException.java │ ├── configuration │ └── LimiterConfiguration.java │ ├── processor │ ├── MethodLimiterProcessor.java │ └── UserLimiterProcessor.java │ └── service │ ├── Limiter.java │ └── impl │ └── DistributedLimiter.java └── resources ├── META-INF ├── services │ └── javax.annotation.processing.Processor ├── spring.factories └── spring.provides └── lua └── rateLimit.lua /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ 25 | /mvnw.cmd 26 | /mvnw 27 | /.mvn/ 28 | /logs/ 29 | src/main/resources/lua/test.lua 30 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # 读我 2 | 3 | ## 功能 4 | 集群QPS控制,在方法上添加注解即可限制QPS 5 | 6 | ## 使用方法 7 | 8 | ### 注解定义 9 | ```java 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.METHOD}) 12 | public @interface Limiter { 13 | 14 | /** 15 | * 限流route 16 | * @return 17 | */ 18 | String route(); 19 | 20 | /** 21 | * 限流次数 22 | * @return 23 | */ 24 | int limit() default 10; 25 | 26 | } 27 | ``` 28 | 29 | ### 资源 30 | > 在pom.xml中添加依赖 31 | ```xml 32 | 33 | com.genxiaogu 34 | rate-limiter-spring-boot-starter 35 | 1.1.0 36 | 37 | 38 | ``` 39 | > 在application.properties中添加redis资源 40 | ```properties 41 | spring.redis.database=0 42 | spring.redis.host=127.0.0.1 43 | spring.redis.port=8888 44 | spring.redis.password=xxx 45 | spring.redis.pool.max-active=50000 46 | spring.redis.pool.max-wait=-1 47 | spring.redis.pool.max-idle=10 48 | spring.redis.pool.min-idle=0 49 | spring.redis.timeout=500 50 | ``` 51 | 52 | > 在需要进行流量控制的方法上添加注解例如 53 | ```java 54 | 55 | 在mapping方法上 56 | @Limiter(route = "test", limit = 100) 57 | @RequestMapping("/test") 58 | public String check(){ 59 | return dataPermissionTest.test() ; 60 | } 61 | 或者在普通方法上 62 | @Limiter(route = "test", limit = 100) 63 | public String test(){ 64 | return "test" ; 65 | } 66 | ``` 67 | 68 | ***注意,Limiter的route参数指代控制方法的唯一key,limit表示1s内允许访问多少次*** 69 | 70 | 71 | ## DEMO 72 | [spring-boot-rate-limiter-demo](https://github.com/gengu/spring-boot-demos/tree/master/spring-boot-rate-limiter-demo) 73 | 74 | ## 使用siege测试 75 | 76 | 77 | > 总结 78 | > > 我尝试把系统的QPS压测到100 79 | 80 | ```javascript 81 | siege -c 50 -t 1 'http://localhost:8080/test' 82 | ``` 83 | 84 | ```properties 85 | Transactions: 1125 hits 86 | Availability: 98.00 % 87 | Elapsed time: 11.80 secs 88 | Data transferred: 0.01 MB 89 | Response time: 0.00 secs 90 | Transaction rate: 95.34 trans/sec 91 | Throughput: 0.00 MB/sec 92 | Concurrency: 0.41 93 | Successful transactions: 1125 94 | Failed transactions: 23 95 | Longest transaction: 0.14 96 | Shortest transaction: 0.00 97 | ``` 98 | 99 | 100 | ## TOList 101 | 102 | * [ ] 统计每个route的拦截次数以及通过次数 103 | * [ ] 统计被拦截的IP地址信息等,预防DDOS很有用 104 | * [ ] 现在抛异常的方案太过粗暴,可以定义接口由客户端来实现逻辑 105 | * [ ] 针对不同的用户进行不同的流量控制 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.genxiaogu 7 | rate-limiter-spring-boot-starter 8 | 2.0.1 9 | jar 10 | rate-limiter-spring-boot-starter 11 | rate-limiter 12 | https://github.com/gengu/rate-limiter-spring-boot-starter 13 | 14 | 15 | org.sonatype.oss 16 | oss-parent 17 | 7 18 | 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | repo 25 | 26 | 27 | 28 | 29 | 30 | genxiaogu 31 | 亘小古 32 | gengugu@foxmail.com 33 | http://www.genxiaogu.com 34 | 35 | owner 36 | 37 | 38 | 39 | junzijian 40 | 君子剑 41 | liuzhe3015@foxmail.com 42 | http://junzijian.github.io 43 | 44 | developer 45 | 46 | 47 | 48 | 49 | 50 | https://github.com/gengu/rate-limiter-spring-boot-starter 51 | 52 | 53 | 54 | UTF-8 55 | UTF-8 56 | 1.8 57 | 1.4.2.RELEASE 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-data-redis 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-dependencies 79 | ${spring-boot.version} 80 | pom 81 | import 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-source-plugin 91 | 92 | 93 | attach-sources 94 | 95 | jar 96 | 97 | 98 | 99 | 100 | 101 | maven-compiler-plugin 102 | 2.3.2 103 | 104 | 1.7 105 | 1.7 106 | 107 | -proc:none 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/advice/MethodRateLimiterAdvisor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.advice; 2 | 3 | import com.genxiaogu.ratelimiter.annotation.Limiter; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.PostConstruct; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | /** 16 | * 静态切入点 17 | * Created by genxiaogu on 2017/7/4. 18 | */ 19 | @Component 20 | @EnableAspectJAutoProxy(proxyTargetClass = true) 21 | public class MethodRateLimiterAdvisor extends StaticMethodMatcherPointcutAdvisor { 22 | 23 | Logger logger = LoggerFactory.getLogger(MethodRateLimiterAdvisor.class) ; 24 | 25 | @Autowired 26 | MethodRateLimiterBeforeInterceptor advice ; 27 | 28 | @PostConstruct 29 | public void init(){ 30 | super.setAdvice(this.advice); 31 | } 32 | 33 | /** 34 | * 只有加了Limiter注解的才需求切入 35 | * @param method 36 | * @param clazz 37 | * @return 38 | */ 39 | @Override 40 | public boolean matches(Method method, Class clazz) { 41 | Method[] methods = clazz.getMethods(); 42 | for (Method mod : methods) { 43 | Limiter methodAnnotation = mod.getAnnotation(Limiter.class); 44 | if (null != methodAnnotation) { 45 | logger.info(String.format("======%s .. %s .. %s .. %s .. %s ======" 46 | , method.getName() 47 | ,this.getPointcut() 48 | ,Thread.currentThread().getName() , 49 | clazz.getCanonicalName(), 50 | mod.getName()) ); 51 | return true; 52 | } 53 | } 54 | return false ; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/advice/MethodRateLimiterBeforeInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.advice; 2 | 3 | import com.genxiaogu.ratelimiter.annotation.Limiter; 4 | import com.genxiaogu.ratelimiter.common.LimiterException; 5 | import com.genxiaogu.ratelimiter.service.impl.DistributedLimiter; 6 | import org.aopalliance.intercept.MethodInterceptor; 7 | import org.aopalliance.intercept.MethodInvocation; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.core.annotation.AnnotationUtils; 12 | import org.springframework.core.annotation.Order; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.Method; 18 | import java.util.HashMap; 19 | 20 | /** 21 | * 方法拦截器:执行前通知 22 | * @author genxiaogu 23 | */ 24 | @Component 25 | @Order 26 | public class MethodRateLimiterBeforeInterceptor implements MethodInterceptor { 27 | 28 | Logger logger = LoggerFactory.getLogger(MethodRateLimiterBeforeInterceptor.class) ; 29 | 30 | @Autowired 31 | private RedisTemplate redisTemplate; 32 | 33 | @Autowired 34 | DistributedLimiter distributedLimiter ; 35 | 36 | /** 37 | * 执行逻辑 38 | * @param methodInvocation 39 | * @return Object 40 | * @throws Throwable 41 | */ 42 | @Override 43 | public Object invoke(MethodInvocation methodInvocation) throws Throwable { 44 | String route = ""; 45 | int limit = 1; 46 | 47 | Method method = methodInvocation.getMethod(); 48 | 49 | for (Annotation annotation : method.getAnnotations()) { 50 | /* 51 | * 如果方法具有Limiter注解,则需要把method,limit拿出来 52 | */ 53 | if (annotation instanceof Limiter) { 54 | Limiter limiter = method.getAnnotation(Limiter.class); 55 | route = limiter.route(); 56 | limit = limiter.limit(); 57 | 58 | if(!distributedLimiter.execute(route , limit)) { 59 | throw new LimiterException("访问太过频繁,请稍后再试!") ; 60 | } 61 | } 62 | } 63 | return methodInvocation.proceed() ; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/advice/UserRateLimiterAdvisor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.advice; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | 6 | import javax.annotation.PostConstruct; 7 | import javax.jws.soap.SOAPBinding.Use; 8 | 9 | import com.genxiaogu.ratelimiter.annotation.Limiter; 10 | import com.genxiaogu.ratelimiter.annotation.UserLimiter; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 16 | import org.springframework.core.annotation.AnnotationUtils; 17 | import org.springframework.stereotype.Component; 18 | 19 | /** 20 | * 静态切入点 21 | * 用于匹配用户层面的切面 22 | * @author genxiaogu 23 | */ 24 | @Component 25 | @EnableAspectJAutoProxy(proxyTargetClass = true) 26 | public class UserRateLimiterAdvisor extends StaticMethodMatcherPointcutAdvisor { 27 | 28 | Logger logger = LoggerFactory.getLogger(UserRateLimiterAdvisor.class) ; 29 | 30 | @Autowired 31 | UserRateLimiterBeforeInterceptor advice ; 32 | 33 | @PostConstruct 34 | public void init(){ 35 | super.setAdvice(this.advice); 36 | } 37 | 38 | /** 39 | * 只有加了UserLimiter注解的才需求切入 40 | * @param method 41 | * @param clazz 42 | * @return 43 | */ 44 | @Override 45 | public boolean matches(Method method, Class clazz) { 46 | Method[] methods = clazz.getMethods(); 47 | for (Method mod : methods) { 48 | Annotation[][] annotations = mod.getParameterAnnotations() ; 49 | for (int i = 0; i < annotations.length; i++) { 50 | for (int j = 0; j < annotations[i].length; j++) { 51 | if(annotations[i][j].annotationType() == UserLimiter.class){ 52 | return true ; 53 | } 54 | } 55 | } 56 | } 57 | return false ; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/advice/UserRateLimiterBeforeInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.advice; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import java.util.HashMap; 6 | 7 | import com.genxiaogu.ratelimiter.annotation.Limiter; 8 | import com.genxiaogu.ratelimiter.annotation.UserLimiter; 9 | import com.genxiaogu.ratelimiter.common.LimiterException; 10 | import com.genxiaogu.ratelimiter.service.impl.DistributedLimiter; 11 | import org.aopalliance.intercept.MethodInterceptor; 12 | import org.aopalliance.intercept.MethodInvocation; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.core.annotation.AnnotationUtils; 17 | import org.springframework.data.redis.core.RedisTemplate; 18 | import org.springframework.stereotype.Component; 19 | 20 | /** 21 | * 用户拦截器:执行前通知 22 | * @author genxiaogu 23 | */ 24 | @Component 25 | public class UserRateLimiterBeforeInterceptor implements MethodInterceptor { 26 | 27 | Logger logger = LoggerFactory.getLogger(UserRateLimiterBeforeInterceptor.class) ; 28 | 29 | 30 | @Autowired 31 | DistributedLimiter distributedLimiter ; 32 | 33 | /** 34 | * 执行逻辑 35 | * @param methodInvocation 36 | * @return Object 37 | * @throws Throwable 38 | */ 39 | @Override 40 | public Object invoke(MethodInvocation methodInvocation) throws Throwable { 41 | String route = ""; 42 | int limit = 1; 43 | String obj = null ; 44 | 45 | Method method = methodInvocation.getMethod(); 46 | 47 | Annotation[][] annotations = method.getParameterAnnotations() ; 48 | for (int i = 0; i < annotations.length ; i++) { 49 | for (int j = 0; j < annotations[i].length ; j++) { 50 | Annotation annotation = annotations[i][j] ; 51 | if(annotation instanceof UserLimiter){ 52 | route = ((UserLimiter)annotation).route() ; 53 | limit = ((UserLimiter)annotation).limit() ; 54 | obj = String.valueOf(methodInvocation.getArguments()[i]) ; 55 | if(!distributedLimiter.execute(route , limit , obj)) { 56 | throw new LimiterException("访问太过频繁,请稍后再试!") ; 57 | } 58 | } 59 | } 60 | } 61 | return methodInvocation.proceed() ; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/annotation/Limiter.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created by genxiaogu on 2017/7/4. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.METHOD}) 13 | public @interface Limiter { 14 | 15 | /** 16 | * 限流route 17 | * @return 18 | */ 19 | String route(); 20 | 21 | /** 22 | * 限流次数 23 | * @return 24 | */ 25 | int limit() default 10; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/annotation/UserLimiter.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created by genxiaogu on 2017/7/20. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.PARAMETER}) 13 | public @interface UserLimiter { 14 | 15 | /** 16 | * 限流route 17 | * @return 18 | */ 19 | String route(); 20 | 21 | /** 22 | * 限流次数 23 | * @return 24 | */ 25 | int limit() default 10; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/common/LimiterException.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.common; 2 | 3 | /** 4 | * @author genxiaogu 5 | */ 6 | public class LimiterException extends RuntimeException{ 7 | private String msg; 8 | 9 | public LimiterException() { 10 | super(); 11 | } 12 | 13 | public LimiterException(String msg) { 14 | super(msg); 15 | this.msg = msg; 16 | } 17 | 18 | public String getMsg() { 19 | return msg; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/configuration/LimiterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.configuration; 2 | 3 | import com.genxiaogu.ratelimiter.advice.MethodRateLimiterAdvisor; 4 | import com.genxiaogu.ratelimiter.advice.MethodRateLimiterBeforeInterceptor; 5 | import com.genxiaogu.ratelimiter.advice.UserRateLimiterAdvisor; 6 | import com.genxiaogu.ratelimiter.advice.UserRateLimiterBeforeInterceptor; 7 | import com.genxiaogu.ratelimiter.service.impl.DistributedLimiter; 8 | import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 13 | import org.springframework.data.redis.core.StringRedisTemplate; 14 | import org.springframework.transaction.annotation.EnableTransactionManagement; 15 | 16 | /** 17 | * Created by wb-lz260260 on 2017/7/5. 18 | */ 19 | @Configuration 20 | @EnableAspectJAutoProxy 21 | @EnableTransactionManagement 22 | public class LimiterConfiguration { 23 | 24 | @Autowired 25 | StringRedisTemplate redisTemplate; 26 | 27 | /** 28 | * 自动代理生成器 29 | * 这个类可以扫描所有的切面类,并为其自动生成代理。 30 | * @return 31 | */ 32 | @Bean 33 | public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ 34 | return new DefaultAdvisorAutoProxyCreator() ; 35 | } 36 | 37 | @Bean 38 | public MethodRateLimiterBeforeInterceptor methodAroundInterceptor(){ 39 | return new MethodRateLimiterBeforeInterceptor() ; 40 | } 41 | 42 | @Bean 43 | public UserRateLimiterBeforeInterceptor userRateLimiterBeforeInterceptor(){ 44 | return new UserRateLimiterBeforeInterceptor() ; 45 | } 46 | 47 | @Bean 48 | public MethodRateLimiterAdvisor methodAdvisor(){ 49 | return new MethodRateLimiterAdvisor() ; 50 | } 51 | 52 | @Bean 53 | public UserRateLimiterAdvisor userAdvisor(){ 54 | return new UserRateLimiterAdvisor() ; 55 | } 56 | 57 | @Bean 58 | public DistributedLimiter distributedLimiter(){ 59 | return new DistributedLimiter(redisTemplate) ; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/processor/MethodLimiterProcessor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.processor; 2 | 3 | import com.genxiaogu.ratelimiter.annotation.Limiter; 4 | import org.springframework.util.StringUtils; 5 | 6 | import javax.annotation.processing.*; 7 | import javax.lang.model.element.Element; 8 | import javax.lang.model.element.Name; 9 | import javax.lang.model.element.TypeElement; 10 | import javax.tools.Diagnostic; 11 | import java.util.Set; 12 | 13 | /** 14 | * 编译阶段检查 15 | * @author genxiaogu 16 | */ 17 | @SupportedAnnotationTypes({"com.genxiaogu.ratelimiter.annotation.Limiter"}) 18 | public class MethodLimiterProcessor extends AbstractProcessor { 19 | 20 | /** 21 | * 方法拦截 22 | */ 23 | public static String limiterName = "com.genxiaogu.ratelimiter.annotation.Limiter" ; 24 | 25 | /** 26 | * 检查注解的适用方法 27 | * @param annotations 28 | * @param roundEnv 29 | * @return 30 | */ 31 | @Override 32 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 33 | 34 | for (TypeElement currentAnnotation : annotations) { 35 | Name qualifiedName = currentAnnotation.getQualifiedName(); 36 | if (qualifiedName.contentEquals(limiterName)) { 37 | Set annotatedElements = roundEnv.getElementsAnnotatedWith(currentAnnotation); 38 | for (Element element : annotatedElements) { 39 | Limiter limiter = element.getAnnotation(Limiter.class); 40 | String router = limiter.route(); 41 | int limit = limiter.limit(); 42 | if (limit <= 0 ) { 43 | String errMsg = "Limiter's argument limit cannot be negative. limit = " + limit ; 44 | Messager messager = this.processingEnv.getMessager(); 45 | messager.printMessage(Diagnostic.Kind.ERROR , errMsg , element); 46 | return true ; 47 | }else if(StringUtils.isEmpty(router)){ 48 | String errMsg = "Limiter's argument router cannot be empty ." ; 49 | Messager messager = this.processingEnv.getMessager(); 50 | messager.printMessage(Diagnostic.Kind.ERROR , errMsg , element); 51 | return true ; 52 | } 53 | } 54 | } 55 | } 56 | return false; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/processor/UserLimiterProcessor.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.processor; 2 | 3 | import java.util.Set; 4 | 5 | import javax.annotation.processing.AbstractProcessor; 6 | import javax.annotation.processing.Messager; 7 | import javax.annotation.processing.RoundEnvironment; 8 | import javax.annotation.processing.SupportedAnnotationTypes; 9 | import javax.lang.model.element.Element; 10 | import javax.lang.model.element.Name; 11 | import javax.lang.model.element.TypeElement; 12 | import javax.tools.Diagnostic; 13 | 14 | import com.genxiaogu.ratelimiter.annotation.Limiter; 15 | import com.genxiaogu.ratelimiter.annotation.UserLimiter; 16 | import org.springframework.util.StringUtils; 17 | 18 | /** 19 | * 编译阶段检查 20 | * @author genxiaogu 21 | */ 22 | @SupportedAnnotationTypes({"com.genxiaogu.ratelimiter.annotation.UserLimiter"}) 23 | public class UserLimiterProcessor extends AbstractProcessor { 24 | 25 | /** 26 | * 参数拦截 27 | */ 28 | public static String userLimiterName = "com.genxiaogu.ratelimiter.annotation.UserLimiter" ; 29 | 30 | /** 31 | * 检查注解的适用方法 32 | * @param annotations 33 | * @param roundEnv 34 | * @return 35 | */ 36 | @Override 37 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 38 | 39 | for (TypeElement currentAnnotation : annotations) { 40 | Name qualifiedName = currentAnnotation.getQualifiedName(); 41 | if (qualifiedName.contentEquals(userLimiterName)) { 42 | Set annotatedElements = roundEnv.getElementsAnnotatedWith(currentAnnotation); 43 | for (Element element : annotatedElements) { 44 | UserLimiter limiter = element.getAnnotation(UserLimiter.class); 45 | String router = limiter.route(); 46 | int limit = limiter.limit(); 47 | if (limit <= 0 ) { 48 | String errMsg = "UserLimiter limit argument cannot be negative. limit = " + limit ; 49 | Messager messager = this.processingEnv.getMessager(); 50 | messager.printMessage(Diagnostic.Kind.ERROR , errMsg , element); 51 | return true ; 52 | }else if(StringUtils.isEmpty(router)){ 53 | String errMsg = "UserLimiter route argument cannot be empty ." ; 54 | Messager messager = this.processingEnv.getMessager(); 55 | messager.printMessage(Diagnostic.Kind.ERROR , errMsg , element); 56 | return true ; 57 | } 58 | } 59 | } 60 | } 61 | return false; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/service/Limiter.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.service; 2 | 3 | /** 4 | * Created by wb-lz260260 on 2017/7/4. 5 | */ 6 | public interface Limiter { 7 | 8 | boolean execute(); 9 | 10 | public boolean execute(String route , Integer limit) ; 11 | 12 | public boolean execute(String route , Integer limit , String obj) ; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/genxiaogu/ratelimiter/service/impl/DistributedLimiter.java: -------------------------------------------------------------------------------- 1 | package com.genxiaogu.ratelimiter.service.impl; 2 | 3 | import com.genxiaogu.ratelimiter.service.Limiter; 4 | import org.apache.log4j.LogManager; 5 | import org.apache.log4j.Logger; 6 | import org.springframework.core.io.ClassPathResource; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.script.DefaultRedisScript; 9 | import org.springframework.scripting.support.ResourceScriptSource; 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * Created by junzijian on 2017/7/4. 15 | */ 16 | public class DistributedLimiter implements Limiter { 17 | 18 | private static final Logger logger = LogManager.getLogger(DistributedLimiter.class); 19 | 20 | public static final String TIME_OUT = "1000"; 21 | 22 | private RedisTemplate redisTemplate; 23 | 24 | private String route; 25 | 26 | private Integer limit; 27 | 28 | /** 29 | * @param redisTemplate 30 | */ 31 | public DistributedLimiter(RedisTemplate redisTemplate, String route, Integer limit) { 32 | this.redisTemplate = redisTemplate; 33 | this.route = route; 34 | this.limit = limit; 35 | } 36 | 37 | public DistributedLimiter(RedisTemplate redisTemplate) { 38 | this.redisTemplate = redisTemplate; 39 | } 40 | 41 | /** 42 | * 第一次如果key存在则从redis删除,并加入到keySet,相当于是初始化 43 | * 44 | * @return 45 | */ 46 | @Override 47 | public boolean execute() { 48 | return execute(route, limit, ""); 49 | } 50 | 51 | @Override 52 | public boolean execute(String route, Integer limit) { 53 | return execute(route, limit, ""); 54 | } 55 | 56 | /** 57 | * 限流实现 58 | * 59 | * @param route 60 | * @param limit 61 | * @param obj 62 | * @return 63 | */ 64 | @Override 65 | public boolean execute(String route, Integer limit, String obj) { 66 | 67 | final String key = route.concat(obj); 68 | 69 | DefaultRedisScript redisScript = new DefaultRedisScript<>(); 70 | redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/rateLimit.lua"))); 71 | redisScript.setResultType(Long.TYPE); 72 | 73 | Object result = redisTemplate.execute(redisScript, new ArrayList() {{add(key);}}, String.valueOf(limit), TIME_OUT); 74 | 75 | if ((long) result == 1) { 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.genxiaogu.ratelimiter.processor.MethodLimiterProcessor 2 | com.genxiaogu.ratelimiter.processor.UserLimiterProcessor -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.genxiaogu.ratelimiter.configuration.LimiterConfiguration -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.provides: -------------------------------------------------------------------------------- 1 | provides : gengugu@foxmail.com -------------------------------------------------------------------------------- /src/main/resources/lua/rateLimit.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by IntelliJ IDEA. 3 | -- User: junzijian 4 | -- Date: 2017/9/21 5 | -- Time: 15:42 6 | -- To change this template use File | Settings | File Templates. 7 | -- rateLimit 8 | 9 | local key = KEYS[1] 10 | local value = 1 11 | local limit = tonumber(ARGV[1]) 12 | local pExpire = ARGV[2] 13 | 14 | if redis.call("SET", key, value, "NX", "PX", pExpire) then 15 | return 1 16 | else 17 | if redis.call("INCR", key) <= limit then 18 | return 1 19 | end 20 | if redis.call("TTL", key) == -1 then 21 | redis.call("PEXPIRE", key, pExpire) 22 | end 23 | end 24 | return 0 25 | 26 | 27 | --------------------------------------------------------------------------------