目錄
簡介
1. 使用Guava的RateLimiter實現限流
添加Guava依賴
實現RateLimiter限流邏輯
限流管理類
控制器中應用限流邏輯
2. 使用計數器實現限流
限流管理類
控制器中應用限流邏輯
簡介
針對某個IP進行限流以防止惡意點擊是一種常見的反爬蟲和防止DoS的措施。限流策略通過對某個IP的訪問頻率進行控制,防止惡意用戶對應用造成負面的影響。
以下是實現限流的步驟和方法,在Java后端通常這樣實現:
1. 使用Guava的RateLimiter實現限流
Guava庫提供了一個簡單而高效的限流工具:RateLimiter,可以方便的實現針對IP的訪問頻率控制。
添加Guava依賴
首先,在pom.xml文件中添加Guava依賴:
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version>
</dependency>
實現RateLimiter限流邏輯
限流管理類
創建一個類來管理針對IP的限流:
import com.google.common.util.concurrent.RateLimiter;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;public class RateLimiterManager {private final ConcurrentMap<String, RateLimiter> 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<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
-
ConcurrentMap<String, RateLimiter>:
- 使用
ConcurrentMap
來存儲每個IP的限流器(RateLimiter
)。 ConcurrentMap
接口允許高效地進行并發訪問和更新,確保線程安全。
- 使用
-
new ConcurrentHashMap<>():
- 實例化一個
ConcurrentHashMap
,它是ConcurrentMap
的常用實現。這種數據結構支持線程安全的讀寫操作,適合限流場景。
- 實例化一個
private final double permitsPerSecond = 1.0; // 每秒允許1次請求
- permitsPerSecond:
- 定義一個double類型的常量
permitsPerSecond
,值為1.0
。 - 表示每秒允許1次請求的限流速率。RateLimiter根據此值來創建相應的限流器。
- 定義一個double類型的常量
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();
- rateLimiter.tryAcquire():
- 嘗試獲取一個請求許可。在給定的限流速率范圍內,如果成功獲取許可,則返回
true
,否則返回false
。 tryAcquire
使得在請求達到速率限制時,予以限制,而不使請求排隊。
- 嘗試獲取一個請求許可。在給定的限流速率范圍內,如果成功獲取許可,則返回
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
如果我們發現請求達到了上限的時候,設置相應的狀態碼,然后前端會根據相應的狀態碼來完成請求,同時就防止這個ip然后再進行訪問。
2. 使用計數器實現限流
計數器限流比較簡單,通過記錄每個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<String, AtomicInteger> 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();
}
- @Scheduled(fixedRate = 10000):注解用于配置定時任務,每10秒執行一次。
- fixedRate:設定定時任務的執行頻率,這里設置為每10000毫秒(即每10秒)。
- resetCountsScheduled 方法:定時任務調用?
resetCounts
?方法清空計數器。- 日志記錄:記錄定時任務執行。
- 調用 resetCounts:每10秒自動調用?
resetCounts
?方法重置計數器。
public void resetCounts() {log.info("Reset counts");requestCounts.clear();
}
resetCounts:用于重置計數器。
- 日志記錄:記錄重置計數器操作。
- clear:清空?
requestCounts
?中的所有鍵值對,重置所有IP的計數器