限流场景
- 秒死活动,恶意使用脚本抢单,需要防止机械进行抢单
- 某api 被各式各样系统广泛调用,严重消耗网络,内存等资源,需要合理安排限流
- 一下免费的接口提供给用户需要限流和一下测试接口,就需要进行限流了
限流思路
- 通过api 路径的作为key ,访问次数为value 的方式对某一用户的某一请求进行唯一标识
- 每次访问的时候判断key 是否存在,是否count 超过了限制的访问次数
- 落访问次数超出限制,则应response 返回msg 请求过于频繁给前端展示信息
实现
- 定义一个aop 注解接口类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
int seconds();
int maxCount();
}
- aop 加redis 限流
@Aspect
@Component
public class AccessLimitInterceptor{
@Autowired
RedisTemplate redisTemplate;
@Pointcut("@annotation(com.ljh.redis.annotation.AccessLimit)")
private void cutMethod() {
}
@Before("cutMethod()")
public void begin(JoinPoint point) throws Exception {
MethodSignature signature = (MethodSignature)point.getSignature();
//获取次数
int maxCount = signature.getMethod().getAnnotation(AccessLimit.class).maxCount();
//获取时间多少秒
int seconds = signature.getMethod().getAnnotation(AccessLimit.class).seconds();
//获取request 对象
ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attr.getRequest();
HttpServletResponse response = attr.getResponse();
//限流的key
String ip = request.getRemoteAddr();
String key = ip+":8080"+request.getServletPath();
//获取 访问的次数
Integer count = (Integer) redisTemplate.boundValueOps(key).get();
//如果是第一次访问
if(count==null||count==-1){
redisTemplate.boundValueOps(key).set(1);
//设置过期时间
redisTemplate.boundValueOps(key).expire(Duration.ofSeconds(seconds));
}
//如果访问次数<最大次数,加1
if (count<maxCount){
redisTemplate.boundValueOps(key).increment();
} else { //>= 最大次数
response.getWriter().println("IP 限流中");
throw new Exception("IP 限流中");
}
}
}