使用 redis 实现分布式接口限流注解 RedisLimit

立山  金牌会员 | 2023-9-22 11:38:26 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 925|帖子 925|积分 2775

前言


  • 很多时候,由于种种不可描述的原因,我们需要针对单个接口实现接口限流,防止访问次数过于频繁。这里就用 redis+aop 实现一个限流接口注解
@RedisLimit 代码

点击查看RedisLimit注解代码
  1. import java.lang.annotation.*;
  2. /**
  3. * 功能:分布式接口限流注解
  4. * @author love ice
  5. * @create 2023-09-18 15:43
  6. */
  7. @Target({ElementType.TYPE,ElementType.METHOD})
  8. @Documented
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface RedisLimit {
  11.     /**
  12.      * redis中唯一key,一般用方法名字做区分
  13.      * 作用: 针对不同接口,做不同的限流控制
  14.      */
  15.     String key() default "";
  16.     /**
  17.      * 限流时间内允许访问次数 默认1
  18.      */
  19.     long permitsPerSecond() default 1;
  20.     /**
  21.      * 限流时间,单位秒 默认60秒
  22.      */
  23.     long expire() default 60;
  24.     /**
  25.      * 限流提示信息
  26.      */
  27.     String msg() default "接口限流,请稍后重试";
  28. }
复制代码
AOP代码

点击查看aop代码
  1. import com.aliyuncs.utils.StringUtils;
  2. import com.test.redis.Infrastructure.annotation.RedisLimit;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.aspectj.lang.JoinPoint;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. import org.aspectj.lang.annotation.Pointcut;
  8. import org.aspectj.lang.reflect.MethodSignature;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.core.io.ClassPathResource;
  11. import org.springframework.data.redis.core.StringRedisTemplate;
  12. import org.springframework.data.redis.core.script.DefaultRedisScript;
  13. import org.springframework.scripting.support.ResourceScriptSource;
  14. import org.springframework.stereotype.Component;
  15. import javax.annotation.PostConstruct;
  16. import java.lang.reflect.Method;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.List;
  20. /**
  21. * redis限流切面
  22. *
  23. * @author love ice
  24. * @create 2023-09-18 15:44
  25. */
  26. @Slf4j
  27. @Aspect
  28. @Component
  29. public class RedisLimitAop {
  30.     @Autowired
  31.     private StringRedisTemplate stringRedisTemplate;
  32.     private DefaultRedisScript<Long> redisScript;
  33.     @PostConstruct
  34.     public void init() {
  35.         redisScript = new DefaultRedisScript<>();
  36.         redisScript.setResultType(Long.class);
  37.         // 执行 lua 脚本
  38.         ResourceScriptSource resourceScriptSource = new ResourceScriptSource(new ClassPathResource("rateLimiter.lua"));
  39.         redisScript.setScriptSource(resourceScriptSource);
  40.     }
  41.     @Pointcut("@annotation(com.test.redis.Infrastructure.annotation.RedisLimit)")
  42.     private void check() {
  43.     }
  44.     @Before("check()")
  45.     private void before(JoinPoint joinPoint) {
  46.         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  47.         Method method = signature.getMethod();
  48.         // 拿到 RedisLimit 注解,如果存在则说明需要限流
  49.         RedisLimit redisLimit = method.getAnnotation(RedisLimit.class);
  50.         if (redisLimit != null) {
  51.             // 获取 redis 的 key
  52.             String key = redisLimit.key();
  53.             String className = method.getDeclaringClass().getName();
  54.             String name = method.getName();
  55.             String limitKey = key + className + name;
  56.             log.info("限流的key:{}", limitKey);
  57.             if (StringUtils.isEmpty(key)) {
  58.                 // 这里是自定义异常,为了方便写成了 RuntimeException
  59.                 throw new RuntimeException("code:101,msg:接口中 key 参数不能为空");
  60.             }
  61.             long limit = redisLimit.permitsPerSecond();
  62.             long expire = redisLimit.expire();
  63.             // 把 key 放入 List 中
  64.             List<String> keys = new ArrayList<>(Collections.singletonList(key));
  65.             Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limit), String.valueOf(expire));
  66.             log.info("Access try count is {} for key ={}", count, keys);
  67.             if (count != null && count == 0) {
  68.                 log.debug("令牌桶={}, 获取令牌失效,接口触发限流", key);
  69.                 throw new RuntimeException("code:10X, redisLimit.msg()");
  70.             }
  71.         }
  72.     }
  73. }
复制代码
lua脚本代码

注意:脚本代码是放在 resources 文件下的,它的类型是 txt,名称后缀是lua。如果你不想改名称,就使用我写好的全名--> rateLimiter.lua
点击查看脚本代码
  1. --获取KEY
  2. local key = KEYS[1]
  3. local limit = tonumber(ARGV[1])
  4. local curentLimit = tonumber(redis.call('get', key) or "0")
  5. if curentLimit + 1 > limit
  6.     then return 0
  7. else
  8.     -- 自增长 1
  9.     redis.call('INCRBY', key, 1)
  10.     -- 设置过期时间
  11.     redis.call('EXPIRE', key, ARGV[2])
  12.     return curentLimit + 1
  13. end
复制代码
最后为了照顾纯小白,给大家看一下我的目录结构


出处:https://www.cnblogs.com/LoveBB/本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

立山

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表