介绍
Spring Boot 接口限流是防止接口被频繁请求而导致服务器负载过重或服务崩溃的一种策略。通过限流,我们可以控制单位时间内允许的请求次数,确保系统的稳定性。限流可以帮助防止恶意请求、保护系统资源,并优化 API 的可用性,避免因过多请求导致服务不可用。
自定义注解
java
@Retention(RetentionPolicy.RUNTIME) //运行时使用
@Target({ElementType.METHOD}) // 应用到方法和类上
public @interface ApiLimitation {
int seconds() default 5; //多少秒访问
int maxCount() default 5; //最大次数
//默认5秒可以访问5次
}
依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置文件
yml
spring:
redis:
# Redis服务器地址
host: 127.0.0.1
# Redis服务器端口号
port: 6379
# 使用的数据库索引,默认是0
database: 0
# 连接超时时间
timeout: 1800000
# 设置密码
# password: "123456"
lettuce:
pool:
# 最大阻塞等待时间,负数表示没有限制
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 5
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中最大连接数,负数表示没有限制
max-active: 20
拦截器
java
@Component
public class RequestInterceptor implements HandlerInterceptor {
// RedisTemplate 用于与 Redis 交互
private final RedisTemplate<Object, Object> redisTemplate;
// 构造函数,注入 RedisTemplate
public RequestInterceptor(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检查处理的 handler 是否是 HandlerMethod(即具体的控制器方法)
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法上的 ApiLimitation 注解
ApiLimitation methodAnnotation = handlerMethod.getMethodAnnotation(ApiLimitation.class);
// 如果没有 ApiLimitation 注解,则跳过限流逻辑,允许访问
if (methodAnnotation == null) {
return true;
}
// 获取注解中的配置,设置时间窗口和最大访问次数
int time = methodAnnotation.seconds(); // 限制的时间窗口(秒)
int count = methodAnnotation.maxCount(); // 最大请求次数
// 获取客户端的 IP 地址
String ip = request.getRemoteAddr();
// 组合 key,格式为 "ip:请求路径"
String key = ip + ":" + request.getServletPath();
List<Object> keys = Collections.singletonList(key);
// 创建 Redis 脚本对象,用于执行 Lua 脚本
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText()); // 设置 Lua 脚本内容
redisScript.setResultType(Long.class); // 设置返回值类型为 Long
// 执行 Lua 脚本进行访问频率控制
Long number = redisTemplate.execute(redisScript, keys, count, time);
// 如果返回值为空或者访问次数超过最大限制,表示请求过于频繁,拒绝访问
if (number == null || number.intValue() > count) {
response.getWriter().write("访问频繁"); // 返回 "访问频繁" 信息给客户端
return false; // 拒绝访问
}
// 允许访问
return true;
}
// 如果不是处理具体方法,默认允许访问
return HandlerInterceptor.super.preHandle(request, response, handler);
}
// 判断对象是否为 null 的工具方法
public static boolean isNull(Object object) {
return object == null;
}
// 返回用于限制访问频率的 Lua 脚本内容
private String limitScriptText() {
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" + // 如果当前访问次数已经超过最大次数,则返回当前次数
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" + // 否则,增加访问次数
"if tonumber(current) == 1 then\n" + // 如果是第一次访问,设置 key 的过期时间
" redis.call('expire', key, time)\n" + // 设置过期时间,避免 Redis 中的 key 永久存在
"end\n" +
"return tonumber(current);"; // 返回当前的访问次数
}
}
注册拦截器
java
@Configuration //表示该类为配置类
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final RequestInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
//拦截所有的请求
// registry.addInterceptor(interceptor)
// .addPathPatterns("/user")//需要拦截的请求
// .excludePathPatterns("/login");//不需要拦截的请求
}
}
控制器
java
@RestController
public class UserController {
@GetMapping("/info")
@ApiLimitation(seconds = 5,maxCount = 2) //五秒钟只可以访问2次
public String getInfo(){
return "成功";
}
}
