一、引言
在高并发的互联网应用中,系统稳定性面临严峻挑战。恶意攻击、爬虫、以及不合理的接口调用都可能导致系统资源耗尽,影响正常用户体验。为了保障系统的稳定性和可用性,对请求进行限流是至关重要的技术手段。本文将深入探讨如何在 spring boot 中实现 ip 限流,包括其原理、使用场景、优缺点、多种实现方式,并给出完整的代码案例,助您构建更加健壮的应用。
二、ip 限流原理:深入解析
ip 限流,顾名思义,就是根据客户端的 ip 地址对请求进行频率限制。其核心在于控制每个 ip 地址在单位时间内允许访问的次数,从而避免单个 ip 大量占用资源,保证系统整体的公平性。以下介绍两种常用的限流算法:
2.1 令牌桶算法 (token bucket)
- 工作机制:
- 系统以恒定速率 (
r) 向令牌桶中放入令牌。 - 每个请求需要从令牌桶中获取一个令牌才能被处理。
- 如果令牌桶为空,则请求被拒绝或等待。
- 令牌桶具有最大容量 (
b),当令牌数量达到上限时,新添加的令牌会被丢弃。
- 系统以恒定速率 (
- 优点:
- 平滑突发流量: 允许短时间内突发大量请求,因为令牌桶中可能积累了足够的令牌。
- 容错性高: 即使短时间内没有请求,令牌也会持续积累,允许后续的请求 burst。
- 动态适应: 更适合处理动态变化的请求,可以在一定程度上应对流量高峰。
2.2 漏桶算法 (leaky bucket)
- 工作机制:
- 请求被放入漏桶中,漏桶以恒定速率 (
r) 处理请求。 - 当请求的速率超过漏桶的处理能力时,多余的请求会在桶中堆积。
- 如果桶满,则新的请求会被丢弃。
- 请求被放入漏桶中,漏桶以恒定速率 (
- 优点:
- 严格控制速率: 能严格控制输出速率,保证请求以固定速度被处理。
- 平滑输出: 可以将突发流量转化为平滑的输出流量。
- 缺点:
- 难以应对突发流量: 即使系统资源充足,也无法快速处理突发请求,容易导致请求被拒绝。
2.3 令牌桶与漏桶算法对比
| 特性 | 令牌桶算法 | 漏桶算法 |
|---|---|---|
| 流量控制 | 允许突发流量,平均速率限制 | 严格控制速率,平滑输出 |
| 适用场景 | 需要容忍一定突发流量的场景,如 api 限流 | 对速率有严格要求的场景,如消息队列削峰填谷 |
| 实现复杂度 | 相对简单 | 相对简单 |
| 动态调整 | 容易实现动态调整令牌生成速率和桶容量 | 调整输出速率和桶容量相对困难 |
三、ip 限流的使用场景:应用广泛
ip 限流作为一种基础且有效的限流手段,在各种场景中都有广泛的应用:
3.1 api 网关限流
在 api 网关层实现 ip 限流,可以统一管理和控制所有 api 的请求频率,是微服务架构中保护后端服务的关键措施。
3.2 web 应用安全防护
对登录、注册、找回密码等敏感接口进行 ip 限流,防止暴力 破解和恶意注册,增强应用的安全性。
3.3 微服务架构
在微服务架构中,对每个微服务的接口进行 ip 限流,保障服务的稳定性和可用性,防止服务雪崩。
3.4 防止恶意爬虫
限制爬虫程序的访问频率,防止爬虫过度抓取数据,影响正常用户体验。
3.5 控制资源使用
在共享资源的系统中,限制单个 ip 地址的资源使用,确保每个用户都能公平地使用资源。
四、ip 限流的优缺点分析:理性看待
4.1 优点
- 精准控制: 可以针对每个 ip 地址进行精确的请求限制,有效防止单个 ip 的过度请求。
- 实现简单: 相比于其他复杂的限流策略,ip 限流的实现相对简单,不需要复杂的算法和配置。
- 易于维护: 由于每个 ip 的限流规则相对独立,维护和管理起来比较方便。
4.2 缺点
- ip 伪装绕过: 恶意攻击者可以通过 ip 代理或 vpn 等方式伪装自己的 ip 地址,绕过 ip 限流的限制。
- 影响正常用户: 在某些情况下,正常用户可能会因为网络环境等原因被错误地限流,影响用户体验。
- ip 地址池问题: 如果多个用户共享同一个公网 ip 地址,可能会导致正常用户的请求被错误地限流。
- 无法区分用户: ip 限流只能根据 ip 地址进行限制,无法区分不同的用户,可能导致恶意用户利用合法用户的 ip 地址发起攻击。
- 不能应对分布式攻击: 对于分布式拒绝服务(ddos)攻击,ip 限流只能对单个 ip 进行限制,无法有效应对大量不同 ip 地址的攻击。
- 维护成本: 如果需要对大量 ip 地址进行限流,维护成本可能会比较高,需要考虑使用更高效的存储方案,例如 redis 或数据库。
五、spring boot 实现 ip 限流:代码案例
以下提供两种基于 spring boot 的 ip 限流实现方式:
5.1 基于 guava ratelimiter 的 ip 限流过滤器(推荐)
这种方式使用 guava 的 ratelimiter 实现令牌桶算法,简单高效,易于配置。
1. 项目搭建:
创建一个 spring boot 项目,并添加以下依赖:
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>com.google.guava</groupid>
<artifactid>guava</artifactid>
<version>31.1-jre</version>
</dependency>
</dependencies>2. 创建 ratelimiterservice:
import com.google.common.cache.cachebuilder;
import com.google.common.cache.cacheloader;
import com.google.common.cache.loadingcache;
import com.google.common.util.concurrent.ratelimiter;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.service;
import java.util.concurrent.timeunit;
@service
public class ratelimiterservice {
private static final logger logger = loggerfactory.getlogger(ratelimiterservice.class);
@value("${rate.limit.permitspersecond:10}") // 默认值 10
private double permitspersecond;
// 使用 loadingcache 缓存每个 ip 的 ratelimiter
private final loadingcache<string, ratelimiter> ratelimitercache = cachebuilder.newbuilder()
.expireafterwrite(1, timeunit.minutes) // 缓存 1 分钟
.build(new cacheloader<string, ratelimiter>() {
@override
public ratelimiter load(string ip) {
logger.info("creating ratelimiter for ip: {}, rate: {}", ip, permitspersecond);
return ratelimiter.create(permitspersecond);
}
});
/**
* 尝试获取令牌
* @param ip 客户端 ip 地址
* @return true: 允许访问, false: 限流
*/
public boolean tryacquire(string ip) {
ratelimiter ratelimiter = ratelimitercache.getunchecked(ip);
return ratelimiter.tryacquire();
}
}3. 创建 ip 限流过滤器:
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.component;
import org.springframework.web.filter.onceperrequestfilter;
import jakarta.servlet.filterchain;
import jakarta.servlet.servletexception;
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import java.io.ioexception;
@component
public class ipratelimitfilter extends onceperrequestfilter {
private static final logger logger = loggerfactory.getlogger(ipratelimitfilter.class);
@autowired
private ratelimiterservice ratelimiterservice;
@value("${rate.limit.enabled:true}")
private boolean ratelimitenabled;
@value("${rate.limit.excludepatterns:}") // 排除的url,用逗号分隔
private string excludepatterns;
@override
protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain)
throws servletexception, ioexception {
if (!ratelimitenabled) {
filterchain.dofilter(request, response);
return; // 如果禁用限流,直接放行
}
// 排除不需要限流的url
string requesturi = request.getrequesturi();
if (excludepatterns != null && !excludepatterns.isempty()) {
string[] patterns = excludepatterns.split(",");
for (string pattern : patterns) {
if (requesturi.startswith(pattern.trim())) {
filterchain.dofilter(request, response);
return;
}
}
}
string clientip = getclientip(request);
if (ratelimiterservice.tryacquire(clientip)) {
filterchain.dofilter(request, response); // 允许访问
} else {
logger.warn("rate limit exceeded for ip: {}", clientip);
response.setstatus(httpservletresponse.sc_too_many_requests);
response.getwriter().write("too many requests from this ip.");
}
}
private string getclientip(httpservletrequest request) {
string xffheader = request.getheader("x-forwarded-for");
if (xffheader == null) {
return request.getremoteaddr();
}
return xffheader.split(",")[0];
}
}4. 配置过滤器:
import org.springframework.boot.web.servlet.filterregistrationbean;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
public class filterconfig {
@bean
public filterregistrationbean<ipratelimitfilter> ipratelimitfilterregistration(ipratelimitfilter ipratelimitfilter) {
filterregistrationbean<ipratelimitfilter> registration = new filterregistrationbean<>();
registration.setfilter(ipratelimitfilter);
registration.addurlpatterns("/*"); // 对所有请求进行限流
registration.setorder(1);
return registration;
}
}5. 创建测试控制器:
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
public class testcontroller {
@getmapping("/test")
public string test() {
return "hello, this is a test api.";
}
}6. application.properties 配置:
# 限流配置 rate.limit.enabled=true rate.limit.permitspersecond=10 rate.limit.excludepatterns=/health,/metrics # 排除的url
5.2 基于 concurrenthashmap 的 ip 限流
import org.springframework.http.httpstatus;
import org.springframework.stereotype.component;
import org.springframework.web.servlet.handlerinterceptor;
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import java.util.map;
import java.util.concurrent.concurrenthashmap;
import java.util.concurrent.atomic.atomicinteger;
@component
public class iprequestratelimiter implements handlerinterceptor {
private final int request_limit = 10; // 每秒最大请求数
private final map<string, atomicinteger> requestcountsperip = new concurrenthashmap<>();
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
string ipaddress = request.getremoteaddr();
// 1. 初始化计数器
requestcountsperip.computeifabsent(ipaddress, k -> new atomicinteger(0));
// 2. 检查请求是否超限
if (requestcountsperip.get(ipaddress).incrementandget() > request_limit) {
response.setstatus(httpstatus.too_many_requests.value());
response.getwriter().write("too many requests from this ip. please try again later.");
return false; // 阻止请求
}
return true; // 允许请求
}
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) throws exception {
string ipaddress = request.getremoteaddr();
// 3. 请求完成后,减少计数
requestcountsperip.get(ipaddress).decrementandget();
}
}注意: 此方式只是一个简易的实现,存在时间窗口不精准等问题,生产环境不建议使用.
5.3 测试
启动 spring boot 项目,使用工具(如 postman)模拟不同 ip 地址的请求。当某个 ip 的请求频率超过配置的限制时,会收到 too many requests from this ip. 的响应。
六、进阶特性:提升 ip 限流的可靠性
- 动态调整限流规则: 可以根据系统负载情况动态调整
permitspersecond的值,实现更灵活的限流策略。可以通过 spring cloud config 等配置中心实现动态更新。 - 黑名单和白名单: 支持黑名单和白名单 ip 地址,对特定的 ip 地址进行特殊处理。可以使用 redis 等缓存存储黑白名单,并定期更新。
- 监控和告警: 集成监控系统(如 prometheus、grafana),实时监控 ip 限流的效果,并设置告警规则,及时发现异常情况。
- 分布式限流: 对于分布式系统,需要使用分布式锁或 redis 等技术实现分布式限流,保证所有节点使用相同的限流规则。
- 更精细化的限流维度: 可以结合用户 id、api 接口等维度进行更精细化的限流,例如限制每个用户每天可以访问某个 api 接口的次数。
七、总结
在 spring boot 中实现 ip 限流是一种简单而有效的方式来保障系统的稳定性和可用性。通过本文的详细介绍和代码案例,相信您已经掌握了 ip 限流的基本原理和实现方式。在实际应用中,请根据您的具体需求选择合适的限流算法和实现方式,并结合其他安全措施,构建更加健壮的系统。请记住,安全策略应该是全面的,限流只是其中一部分。
到此这篇关于spring boot 实现 ip 限流(保障系统稳定性的关键技术)的文章就介绍到这了,更多相关spring boot ip 限流内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论