在Spring Boot中,可以通过自定义拦截器(Interceptor)结合Redis或内存计数器实现接口限流。以下是两种典型实现方式及代码示例:
方案一:基于Redis + Lua脚本的分布式限流
核心逻辑
- Redis配置:使用Lua脚本保证原子性操作(计数+过期时间设置)。
- 拦截器:拦截请求,通过Redis统计IP或用户维度的访问次数。
- 注册拦截器:指定拦截路径和排除路径。
代码实现
1. Redis配置类
swift
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public DefaultRedisScript<Long> rateLimitScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local expire = tonumber(ARGV[2])\n" +
"local current = redis.call('INCR', key)\n" +
"if current == 1 then\n" +
" redis.call('EXPIRE', key, expire)\n" +
"end\n" +
"return current > limit and 1 or 0"
);
script.setResultType(Long.class);
return script;
}
}
2. 限流拦截器
java
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DefaultRedisScript<Long> rateLimitScript;
private static final int DEFAULT_LIMIT = 60; // 每分钟60次
private static final int DEFAULT_TIMEOUT = 60; // 60秒过期
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
String key = "rate_limit:" + ip + ":" + uri.split("/")[1]; // 按接口前缀分组
Long result = redisTemplate.execute(
rateLimitScript,
Collections.singletonList(key),
DEFAULT_LIMIT, DEFAULT_TIMEOUT
);
if (result != null && result == 1) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests");
return false;
}
return true;
}
}
3. 注册拦截器
typescript
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
方案二:基于内存计数器的单机限流
核心逻辑
- 拦截器 :使用
ConcurrentHashMap
存储IP和访问时间戳。 - 滑动窗口:统计1分钟内的请求数,超限则拒绝。
代码实现
java
public class RateLimitingInterceptor implements HandlerInterceptor {
private final ConcurrentMap<String, Long> requestCounts = new ConcurrentHashMap<>();
private static final long ALLOWED_REQUESTS_PER_MINUTE = 60;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientIp = request.getRemoteAddr();
long currentTime = System.currentTimeMillis();
// 清理过期请求
requestCounts.entrySet().removeIf(entry ->
currentTime - entry.getValue() > TimeUnit.MINUTES.toMillis(1)
);
// 统计当前窗口请求数
long count = requestCounts.values().stream()
.filter(timestamp -> currentTime - timestamp < TimeUnit.MINUTES.toMillis(1))
.count();
if (count >= ALLOWED_REQUESTS_PER_MINUTE) {
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
response.getWriter().write("请求过于频繁");
return false;
}
requestCounts.put(clientIp, currentTime);
return true;
}
}
关键对比与选择建议
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Redis + Lua | 分布式环境,高精度限流 | 原子性操作,支持分布式,可动态调整参数 | 依赖Redis,网络开销较大 |
内存计数器 | 单机环境,简单场景 | 无外部依赖,实现简单 | 不支持分布式,重启后数据丢失 |
扩展建议:
- 动态配置 :将限流参数(如
DEFAULT_LIMIT
)改为从配置中心读取。 - 注解化 :结合自定义注解(如
@RateLimit
)实现更灵活的限流规则。
两种方案均能有效实现接口限流,根据项目需求选择即可。