├── .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 extends TypeElement> annotations, RoundEnvironment roundEnv) {
33 |
34 | for (TypeElement currentAnnotation : annotations) {
35 | Name qualifiedName = currentAnnotation.getQualifiedName();
36 | if (qualifiedName.contentEquals(limiterName)) {
37 | Set extends Element> 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 extends TypeElement> annotations, RoundEnvironment roundEnv) {
38 |
39 | for (TypeElement currentAnnotation : annotations) {
40 | Name qualifiedName = currentAnnotation.getQualifiedName();
41 | if (qualifiedName.contentEquals(userLimiterName)) {
42 | Set extends Element> 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 |
--------------------------------------------------------------------------------