目录
简介
1. 使用Guava的RateLimiter实现限流
添加Guava依赖
实现RateLimiter限流逻辑
限流管理类
控制器中应用限流逻辑
2. 使用计数器实现限流
限流管理类
控制器中应用限流逻辑
针对某个IP进行限流以防止恶意点击是一种常见的反爬虫和防止DoS的措施。限流策略通过对某个IP的访问频率进行控制,防止恶意用户对应用造成负面的影响。
以下是实现限流的步骤和方法,在Java后端通常这样实现:
Guava库提供了一个简单而高效的限流工具:RateLimiter,可以方便的实现针对IP的访问频率控制。
首先,在pom.xml文件中添加Guava依赖:
com.google.guava
guava
30.1-jre
创建一个类来管理针对IP的限流:
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class RateLimiterManager {
private final ConcurrentMap rateLimiterMap = new ConcurrentHashMap<>();
private final double permitsPerSecond = 1.0; // 每秒允许1次请求
public boolean tryAcquire(String ip) {
RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(ip, k -> RateLimiter.create(permitsPerSecond));
return rateLimiter.tryAcquire();
}
}
在Spring Boot控制器中应用限流逻辑:
import com.xfusion.rate1.limit.RateLimiterManager;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("/test")
public class TestController {
private final RateLimiterManager rateLimiterManager=new RateLimiterManager();
@RequestMapping("/t1")
public void test1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String param=request.getRemoteAddr();
if(rateLimiterManager.tryAcquire(param)) {
response.getWriter().write("Request processed for " + param);
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Request limit for " + param);
}
}
}
private final ConcurrentMap rateLimiterMap = new ConcurrentHashMap<>();
ConcurrentMap
ConcurrentMap
来存储每个IP的限流器(RateLimiter
)。ConcurrentMap
接口允许高效地进行并发访问和更新,确保线程安全。new ConcurrentHashMap<>():
ConcurrentHashMap
,它是ConcurrentMap
的常用实现。这种数据结构支持线程安全的读写操作,适合限流场景。private final double permitsPerSecond = 1.0; // 每秒允许1次请求
permitsPerSecond
,值为1.0
。RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(ip, k -> RateLimiter.create(permitsPerSecond));
computeIfAbsent:
computeIfAbsent
方法用于检查map中是否已经存在为该IP准备的RateLimiter
。k -> RateLimiter.create(permitsPerSecond)
创建一个新的RateLimiter
。这个lambda部分表示为未存在的IP创建一个新的RateLimiter
,限流速率为permitsPerSecond
。RateLimiter.create(permitsPerSecond):
RateLimiter
类的静态方法create
,以指定速率创建一个新的限流器实例。这使得每秒最多处理一个请求。return rateLimiter.tryAcquire();
true
,否则返回false
。tryAcquire
使得在请求达到速率限制时,予以限制,而不使请求排队。response.setStatus(HttpServletResponse.SC_FORBIDDEN);
如果我们发现请求达到了上限的时候,设置相应的状态码,然后前端会根据相应的状态码来完成请求,同时就防止这个ip然后再进行访问。
计数器限流比较简单,通过记录每个IP的请求次数并在指定时间窗口内进行限流。
创建一个类来管理限流逻辑:
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Configuration
@Component
public class CounterRateLimiter {
private final ConcurrentHashMap requestCounts = new ConcurrentHashMap<>();
private final int maxRequestsPerMinute = 10;
public boolean tryAcquire(String ipAddress) {
AtomicInteger requestCount = requestCounts.computeIfAbsent(ipAddress, k -> new AtomicInteger(0));
int currentCount = requestCount.incrementAndGet();
log.info("IP: " + ipAddress + ", Current Count: " + currentCount);
return currentCount <= maxRequestsPerMinute;
}
public void resetCounts() {
log.info("Reset counts");
requestCounts.clear();
}
@Scheduled(fixedRate = 10000)
public void resetCountsScheduled() {
log.info("刷新这个ip的次数");
resetCounts();
}
}
在开启定时任务的时候,要在Application上添加上@EnableScheduling这个注解
@SpringBootApplication
@Configuration
@EnableScheduling
public class Rate1Application {
public static void main(String[] args) {
SpringApplication.run(Rate1Application.class, args);
}
}
在Spring Boot控制器中应用限流逻辑,并定期重置计数器:
private final CounterRateLimiter counterRateLimiter;
public TestController(CounterRateLimiter counterRateLimiter) {
this.counterRateLimiter = counterRateLimiter;
}
@RequestMapping("/t2")
public void test2(HttpServletRequest request, HttpServletResponse response) throws IOException {
String param = request.getRequestURI();
if (counterRateLimiter.tryAcquire(param)) {
log.info("打印日志1");
response.getWriter().write("Request processed for test2 " + param);
} else {
log.info("打印日志2");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Request limit for test2" + param);
}
}
@Scheduled(fixedRate = 10000)
public void resetCountsScheduled() {
log.info("刷新这个ip的次数");
resetCounts();
}
resetCounts
方法清空计数器。
resetCounts
方法重置计数器。public void resetCounts() {
log.info("Reset counts");
requestCounts.clear();
}
resetCounts:用于重置计数器。
requestCounts
中的所有键值对,重置所有IP的计数器