在spring boot中,可以通过自定义拦截器(interceptor)结合redis或内存计数器实现接口限流。以下是两种典型实现方式及代码示例:
方案一:基于redis + lua脚本的分布式限流
核心逻辑
- redis配置:使用lua脚本保证原子性操作(计数+过期时间设置)。
- 拦截器:拦截请求,通过redis统计ip或用户维度的访问次数。
- 注册拦截器:指定拦截路径和排除路径。
代码实现
1. redis配置类
@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. 限流拦截器
@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. 注册拦截器
@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分钟内的请求数,超限则拒绝。
代码实现
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)实现更灵活的限流规则。
两种方案均能有效实现接口限流,根据项目需求选择即可。
到此这篇关于springboot通过拦截器实现接口限流的两种方案的文章就介绍到这了,更多相关springboot拦截器接口限流内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论