From 57100fe4ab531bd6ed7e6b493910425273439bc8 Mon Sep 17 00:00:00 2001 From: "Yangkai.Shen" <237497819@qq.com> Date: Mon, 30 Sep 2019 16:02:02 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20spring-boot-demo-ratelimit-redis?= =?UTF-8?q?=20=E5=88=86=E5=B8=83=E5=BC=8F=E9=99=90=E6=B5=81=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-demo-ratelimit-redis/README.md | 296 ++++++++++++++++++ .../assets/image-20190930155735300.png | Bin 0 -> 349453 bytes .../assets/image-20190930155856711.png | Bin 0 -> 302433 bytes 3 files changed, 296 insertions(+) create mode 100644 spring-boot-demo-ratelimit-redis/README.md create mode 100644 spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png create mode 100644 spring-boot-demo-ratelimit-redis/assets/image-20190930155856711.png diff --git a/spring-boot-demo-ratelimit-redis/README.md b/spring-boot-demo-ratelimit-redis/README.md new file mode 100644 index 0000000..b5fbfbe --- /dev/null +++ b/spring-boot-demo-ratelimit-redis/README.md @@ -0,0 +1,296 @@ +# spring-boot-demo-ratelimit-redis + +> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Redis 实现分布式限流,旨在保护 API 被恶意频繁访问的问题,是 `spring-boot-demo-ratelimit-guava` 的升级版。 + +## 1. 主要代码 + +### 1.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-ratelimit-redis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-ratelimit-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-ratelimit-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 1.2. 限流注解 + +```java +/** + *

+ * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + *

+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:31 + * @see AnnotationUtils + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + long DEFAULT_REQUEST = 10; + + /** + * max 最大请求数 + */ + @AliasFor("max") long value() default DEFAULT_REQUEST; + + /** + * max 最大请求数 + */ + @AliasFor("value") long max() default DEFAULT_REQUEST; + + /** + * 限流key + */ + String key() default ""; + + /** + * 超时时长,默认1分钟 + */ + long timeout() default 1; + + /** + * 超时时间单位,默认 分钟 + */ + TimeUnit timeUnit() default TimeUnit.MINUTES; +} +``` + +### 1.3. AOP处理限流 + +```java +/** + *

+ * 限流切面 + *

+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:30 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RateLimiterAspect { + private final static String SEPARATOR = ":"; + private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; + private final StringRedisTemplate stringRedisTemplate; + private final RedisScript limitRedisScript; + + @Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)") + public void rateLimit() { + + } + + @Around("rateLimit()") + public Object pointcut(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 + RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); + if (rateLimiter != null) { + String key = rateLimiter.key(); + // 默认用类名+方法名做限流的 key 前缀 + if (StrUtil.isBlank(key)) { + key = method.getDeclaringClass().getName()+StrUtil.DOT+method.getName(); + } + // 最终限流的 key 为 前缀 + IP地址 + // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 + key = key + SEPARATOR + IpUtil.getIpAddr(); + + long max = rateLimiter.max(); + long timeout = rateLimiter.timeout(); + TimeUnit timeUnit = rateLimiter.timeUnit(); + boolean limited = shouldLimited(key, max, timeout, timeUnit); + if (limited) { + throw new RuntimeException("手速太快了,慢点儿吧~"); + } + } + + return point.proceed(); + } + + private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { + // 最终的 key 格式为: + // limit:自定义key:IP + // limit:类名.方法名:IP + key = REDIS_LIMIT_KEY_PREFIX + key; + // 统一使用单位毫秒 + long ttl = timeUnit.toMillis(timeout); + // 当前时间毫秒数 + long now = Instant.now().toEpochMilli(); + long expired = now - ttl; + // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String + Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); + if (executeTimes != null) { + if (executeTimes == 0) { + log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); + return true; + } else { + log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); + return false; + } + } + return false; + } +} +``` + +### 1.4. lua 脚本 + +```lua +-- 下标从 1 开始 +local key = KEYS[1] +local now = tonumber(ARGV[1]) +local ttl = tonumber(ARGV[2]) +local expired = tonumber(ARGV[3]) +-- 最大访问量 +local max = tonumber(ARGV[4]) + +-- 清除过期的数据 +-- 移除指定分数区间内的所有元素,expired 即已经过期的 score +-- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expired +redis.call('zremrangebyscore', key, 0, expired) + +-- 获取 zset 中的当前元素个数 +local current = tonumber(redis.call('zcard', key)) +local next = current + 1 + +if next > max then + -- 达到限流大小 返回 0 + return 0; +else + -- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score] + redis.call("zadd", key, now, now) + -- 每次访问均重新设置 zset 的过期时间,单位毫秒 + redis.call("pexpire", key, ttl) + return next +end +``` + +### 1.5. 接口测试 + +```java +/** + *

+ * 测试 + *

+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:30 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 5) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } + + @RateLimiter(value = 2, key = "测试自定义key") + @GetMapping("/test3") + public Dict test3() { + log.info("【test3】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } +} +``` + +### 1.6. 其余代码参见 demo + +## 2. 测试 + +- 触发限流时控制台打印 + +![image-20190930155856711](assets/image-20190930155856711.png) + +- 触发限流的时候 Redis 的数据 + +![image-20190930155735300](assets/image-20190930155735300.png) + +## 3. 参考 + +- [mica-plus-redis 的分布式限流实现](https://github.com/lets-mica/mica/tree/master/mica-plus-redis) +- [Java并发:分布式应用限流 Redis + Lua 实践](https://segmentfault.com/a/1190000016042927) \ No newline at end of file diff --git a/spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png b/spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png new file mode 100644 index 0000000000000000000000000000000000000000..2018be762e578df7541f20d993dd6cb8469fcf85 GIT binary patch literal 349453 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