RateLimiter概述
RateLimiter是Guava提供的的限流器。它基於令牌桶算法實現,預先設定一個速率,然後按照這個速率生成令牌,每次請求消耗一個令牌。限流是保護高併發系統的三把利器之一,另外兩個是緩存和降級,在秒殺搶購等場景中用來限制併發和請求量,保護自身系統和下游系統不被巨型流量沖垮。
核心原理
RateLimiter的核心是"令牌桶算法"。想象一個桶,這個桶以固定速率往裏面放入小令牌。每次程序想要執行一個操作(比如發起請求)時,需要從桶裏取出一個令牌。如果桶裏有令牌,就可以立即執行;如果沒有令牌,就需要等待直到有新的令牌產生。
Demo示例
基礎限流示例(對應1.0,2.0,3.0,5.0,10.0,100.0,1000.0,10000.0)
package com.cqsym.lmdw1.testguava;
import com.google.common.util.concurrent.RateLimiter;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class RateLimiterBasicDemo {
public static void main(String[] args) {
// 創建一個限流器,每秒生成2個令牌
RateLimiter limiter = RateLimiter.create(1.0);
System.out.println("開始測試基礎限流...");
for (int i = 1; i <= 10; i++) {
// 阻塞式獲取令牌
double waitTime = limiter.acquire();
System.out.println(String.format("第%d個請求,等待時間:%.2f秒,當前時間:%s", i, waitTime, getCurrentTime()));
}
}
private static String getCurrentTime() {
return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
}
}
非阻塞式限流示例
package com.cqsym.lmdw1.testguava;
import com.google.common.util.concurrent.RateLimiter;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
public class RateLimiterNonBlockingDemo {
public static void main(String[] args) {
// 創建限流器,每秒1個令牌
RateLimiter limiter = RateLimiter.create(1.0);
System.out.println("開始測試非阻塞限流...");
for (int i = 1; i <= 5; i++) {
// 嘗試獲取令牌,最多等待100毫秒
boolean acquired = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);
if (acquired) {
System.out.println("請求" + i + ":獲取到令牌,執行業務邏輯");
// 模擬業務處理
processRequest(i);
} else {
System.out.println("請求" + i + ":未獲取到令牌,請求被拒絕");
}
// 模擬請求間隔
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private static void processRequest(int requestId) {
System.out.println(" -> 處理請求" + requestId + "中..." + " 時間:" + getCurrentTime());
try {
Thread.sleep(500); // 模擬處理時間
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(" -> 請求" + requestId + "處理完成" + " 時間:" + getCurrentTime());
}
private static String getCurrentTime() {
return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
}
}
因為在處理任務中消耗了500毫秒,所以理論上1秒鐘只能處理2個,但是RateLimiter是1秒產生1個令牌,但是在獲取令牌這裏設置最多等待100毫秒,所以第二個就直接失敗。
批量獲取令牌示例
package com.cqsym.lmdw1.testguava;
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterBatchDemo {
public static void main(String[] args) {
// 創建限流器,每秒5個令牌
RateLimiter limiter = RateLimiter.create(5.0);
System.out.println("開始測試批量獲取令牌...");
// 批量獲取不同數量的令牌
int[] batchSizes = {1, 3, 2, 4, 1};
for (int i = 0; i < batchSizes.length; i++) {
int permits = batchSizes[i];
double waitTime = limiter.acquire(permits);
System.out.println(String.format("批次%d:獲取%d個令牌,等待時間:%.2f秒,時間:%s",
i + 1, permits, waitTime, getCurrentTime()));
}
}
private static String getCurrentTime() {
return java.time.LocalTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
}
}
SpringBoot集成示例
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Service
public class RateLimiterService {
// 每秒允許10個請求
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
public boolean tryAcquire() {
// 嘗試獲取令牌,最多等待100毫秒
return rateLimiter.tryAcquire(100, java.util.concurrent.TimeUnit.MILLISECONDS);
}
public void acquire() {
rateLimiter.acquire();
}
}
@RestController
public class ApiController {
private final RateLimiterService rateLimiterService;
public ApiController(RateLimiterService rateLimiterService) {
this.rateLimiterService = rateLimiterService;
}
@GetMapping("/api/limited")
public String limitedApi() {
if (!rateLimiterService.tryAcquire()) {
return "請求過於頻繁,請稍後再試";
}
// 執行業務邏輯
return "請求處理成功,時間:" + java.time.LocalDateTime.now();
}
@GetMapping("/api/blocking")
public String blockingApi() {
// 阻塞式獲取令牌
rateLimiterService.acquire();
return "阻塞式請求處理成功,時間:" + java.time.LocalDateTime.now();
}
}
核心API詳解
創建RateLimiter
通過 RateLimiter.create(1) 創建一個限流器,參數代表每秒生成的令牌數 :
// 每秒生成2個令牌
RateLimiter limiter = RateLimiter.create(2.0);
// 創建時指定預熱期(平滑啓動)
RateLimiter limiter = RateLimiter.create(2.0, 1, TimeUnit.SECONDS);
獲取令牌的方式
- 阻塞式獲取:
limiter.acquire(i)以阻塞的方式獲取令牌 - 非阻塞式獲取:
tryAcquire()方法可以設置超時時間
// 阻塞式獲取1個令牌
double waitTime = limiter.acquire();
// 阻塞式獲取多個令牌
double waitTime = limiter.acquire(3);
// 非阻塞式獲取,立即返回
boolean success = limiter.tryAcquire();
// 非阻塞式獲取,最多等待指定時間
boolean success = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);
// 非阻塞式獲取,最多等待指定時間
boolean success = limiter.tryAcquire(3, 100, TimeUnit.MILLISECONDS);