# rateLimiter **Repository Path**: kakalex/rateLimiter ## Basic Information - **Project Name**: rateLimiter - **Description**: 基于redis+lua实现分布式限流组件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-12 - **Last Updated**: 2022-02-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 基于Redis+Lua实现分布式限流组件 配置redis ```java @Configuration public class RedisConfig { // 如果本地也配置了StringRedisTemplate,可能会产生冲突 // 可以指定@Primary,或者指定加载特定的@Qualifier @Bean public RedisTemplate redisTemplate( RedisConnectionFactory factory) { return new StringRedisTemplate(factory); } @Bean public RedisScript loadRedisScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); // 加载lua脚本文件 redisScript.setLocation(new ClassPathResource("ratelimiter.lua")); redisScript.setResultType(java.lang.Boolean.class); return redisScript; } } ``` 配置lua脚本 ```shell script -- 获取方法签名特征 local methodKey = KEYS[1] redis.log(redis.LOG_DEBUG, 'key is', methodKey) -- 调用脚本传入的限流大小 local limit = tonumber(ARGV[1]) -- 获取当前流量大小 local count = tonumber(redis.call('get', methodKey) or "0") -- 是否超出限流阈值 if count + 1 > limit then -- 拒绝服务访问 return false else -- 没有超过阈值 -- 设置当前访问的数量+1 redis.call("INCRBY", methodKey, 1) -- 设置过期时间 redis.call("EXPIRE", methodKey, 1) -- 放行 return true end ``` 限流注解 ```java @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AccessLimiter { int limit() default 1; String methodKey() default ""; } ``` 最重要的限流切面配置 ```java @Aspect @Component @Slf4j public class AccessLimiterAspect { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisScript rateLimitLua; @Pointcut("@annotation(com.alex.demo.limiter.annotation.AccessLimiter)") public void cut() { log.info("cut"); } @Before("cut()") public void before(JoinPoint joinPoint) { // 1. 获得方法签名,作为method Key MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); AccessLimiter annotation = method.getAnnotation(AccessLimiter.class); if (annotation == null) { return; } String key = annotation.methodKey(); Integer limit = annotation.limit(); // 如果没设置methodkey, 从调用方法签名生成自动一个key if (StringUtils.isEmpty(key)) { Class[] type = method.getParameterTypes(); key = method.getClass() + method.getName(); if (type != null) { String paramTypes = Arrays.stream(type) .map(Class::getName) .collect(Collectors.joining(",")); log.info("param types: " + paramTypes); key += "#" + paramTypes; } } log.info("key: {} value: {}", key, limit); // 2. 调用Redis boolean acquired = stringRedisTemplate.execute( rateLimitLua, // Lua script的真身 Lists.newArrayList(key), // Lua脚本中的Key列表 limit.toString() // Lua脚本Value列表 ); if (!acquired) { log.error("your access is blocked, key={}", key); throw new RuntimeException("Your access is blocked"); } } } ``` 测试限流组件 ```java @RestController public class LimterController { @GetMapping("test01") @AccessLimiter(limit = 1, methodKey = "order") public String test01() { return "success"; } } ```