springboot+Redis+AOP实现请求限流器

编程入门 行业动态 更新时间:2024-10-26 02:24:42

springboot+<a href=https://www.elefans.com/category/jswz/34/1771249.html style=Redis+AOP实现请求限流器"/>

springboot+Redis+AOP实现请求限流器

写在开头

本文参考技术帖 程序员那点事
主要对学习经验进行总结,也会加上自己的理解注释。

配置RedisTemplate实例

//配置redis 使用String数据结构
//对key  value 进行序列化
//根据配置连接redis
@Configuration
public class RedisLimiterHelper {@Beanpublic RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {RedisTemplate<String, Serializable> template = new RedisTemplate<>();template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;}
}

限流枚举类

//针对访客和ip进行限流
public enum LimitType {/*** 自定义key*/CUSTOMER,/*** 请求者IP*/IP;
}

自定义注解,用于AOP切点

我们自定义个@Limit注解,注解类型为ElementType.METHOD即作用于方法上。

period表示请求限制时间段,count表示在period这个时间段内允许放行请求的次数。limitType代表限流的类型,可以根据请求的IP、自定义key,如果不传limitType属性则默认用方法名作为默认key

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {/*** 名字*/String name() default "";/*** key*/String key() default "";/*** Key的前缀*/String prefix() default "";/*** 给定的时间范围 单位(秒)*/int period();/*** 一定时间内最多访问次数*/int count();/*** 限流的类型(用户自定义key 或者 请求ip)*/LimitType limitType() default LimitType.CUSTOMER;
}

AOP切面

执行主要的限流功能

@Aspect
@Configuration
public class LimitInterceptor {private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);//用于ip判断private static final String UNKNOWN = "unknown";private final RedisTemplate<String, Serializable> limitRedisTemplate;//构造注入@Autowiredpublic LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {this.limitRedisTemplate = limitRedisTemplate;}@Pointcut("execution(public * *(..)) && @annotation(com.limit.api.Limit)")public void limitPointCut(){} //切点名称,方便通知使用//环绕通知,执行限流业务//@Limit(key = "customer_limit_test", period = 10, count = 3, limitType = LimitType.CUSTOMER) 可根据此注解调用AOP@Around("limitPointCut()")public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();Limit limitAnnotation = method.getAnnotation(Limit.class);LimitType limitType = limitAnnotation.limitType();String name = limitAnnotation.name();String key;int limitPeriod = limitAnnotation.period();int limitCount = limitAnnotation.count();/*** 根据限流类型获取不同的key ,如果不传我们会以方法名作为key* IP类型则使用ipAddress,CUSTOMER则使用注解key*/switch (limitType) {case IP:key = getIpAddress();break;case CUSTOMER:key = limitAnnotation.key();break;default:key = StringUtils.upperCase(method.getName());}//将key转化为list 方便后续调用lua的keys参数ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));try {String luaScript = buildLuaScript();RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);logger.info("Access try count is {} for name={} and key = {}", count, name, key);//执行控制器Controller的业务逻辑if (count != null && count.intValue() <= limitCount) {return pjp.proceed();} else {throw new RuntimeException("You have been dragged into the blacklist");}} catch (Throwable e) {if (e instanceof RuntimeException) {throw new RuntimeException(e.getLocalizedMessage());}throw new RuntimeException("server exception");}}//lua脚本处理限流逻辑//查找key下的访问次数c,若c存在且大于限流值,直接返回//key下的c自增1,若key是第一次新建,给过期时间为限流时间//返回此次访问后的访问次数cpublic String buildLuaScript() {StringBuilder lua = new StringBuilder();lua.append("local c");lua.append("\nc = redis.call('get',KEYS[1])");// 调用超过了最大值,则直接返回lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");lua.append("\nreturn c;");lua.append("\nend");// 执行计算器自加lua.append("\nc = redis.call('incr',KEYS[1])");lua.append("\nif tonumber(c) == 1 then");// 从第一次调用开始限流,设置对应键值的过期lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");lua.append("\nend");lua.append("\nreturn c;");return lua.toString();}//获取ip地址,通过ip限流public String getIpAddress() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

在Controller中可通过注解@Limit的方式调用AOP限流

如一下分析,业务代码省略

@Limit(key = "limitTest", period = 10, count = 3)
key为方法名,10s请求3次
@Limit(key = "customer_limit_test", period = 10, count = 3, limitType = LimitType.CUSTOMER)
key为"customer_limit_test",10s请求3次
@Limit(key = "ip_limit_test", period = 10, count = 3, limitType = LimitType.IP)
key为ipAddress,10s请求3次

更多推荐

springboot+Redis+AOP实现请求限流器

本文发布于:2023-11-17 13:29:46,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1642596.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Redis   springboot   限流器   AOP

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!