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 0000000..2018be7
Binary files /dev/null and b/spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png differ
diff --git a/spring-boot-demo-ratelimit-redis/assets/image-20190930155856711.png b/spring-boot-demo-ratelimit-redis/assets/image-20190930155856711.png
new file mode 100644
index 0000000..31f2f6f
Binary files /dev/null and b/spring-boot-demo-ratelimit-redis/assets/image-20190930155856711.png differ