Guava RateLimiter:高效流量控制實(shí)踐指南
背景
在互聯(lián)網(wǎng)飛速發(fā)展的今天,隨著系統(tǒng)用戶規(guī)模的不斷擴(kuò)大和分布式架構(gòu)的廣泛應(yīng)用,API
接口的穩(wěn)定性和性能成為系統(tǒng)設(shè)計(jì)中至關(guān)重要的因素。無論是應(yīng)對(duì)突發(fā)的流量高峰,還是防止惡意爬蟲的惡意請(qǐng)求,限流策略都已成為現(xiàn)代系統(tǒng)不可或缺的一部分,其主要目的包括但不限于以下幾點(diǎn):
- 保護(hù)后端服務(wù):通過限制單位時(shí)間內(nèi)對(duì)特定接口的訪問次數(shù),可以有效避免因突發(fā)流量或惡意攻擊導(dǎo)致的服務(wù)過載,從而確保后端服務(wù)的穩(wěn)定運(yùn)行。
- 保證用戶體驗(yàn):合理的限流策略可以在不影響正常用戶使用的情況下,控制資源的合理分配,確保大多數(shù)用戶的請(qǐng)求能夠得到及時(shí)響應(yīng),提升整體服務(wù)質(zhì)量。
- 資源優(yōu)化利用:對(duì)于有限的計(jì)算資源,如數(shù)據(jù)庫連接、緩存資源等,通過限流可以避免這些資源被少數(shù)高頻率請(qǐng)求耗盡,確保資源的有效利用。
- 成本控制:云服務(wù)通常按照資源消耗計(jì)費(fèi),不當(dāng)?shù)恼?qǐng)求可能會(huì)導(dǎo)致不必要的成本增加。限流可以幫助企業(yè)更好地控制成本,避免因?yàn)橐馔獾母吡髁慷鴮?dǎo)致的成本激增。
- 防止惡意行為:限流可以作為一道防線,阻止惡意爬蟲、
DDoS
攻擊等非法行為,保護(hù)系統(tǒng)免受損害。 - 數(shù)據(jù)安全:通過限制對(duì)外部數(shù)據(jù)的訪問頻率,可以減少敏感信息泄露的風(fēng)險(xiǎn),特別是在處理個(gè)人隱私數(shù)據(jù)時(shí)尤為重要。
- 合規(guī)性:某些行業(yè)有特定的數(shù)據(jù)訪問規(guī)則和限制,實(shí)施限流有助于滿足這些合規(guī)要求,避免法律風(fēng)險(xiǎn)。
Guava
提供了多種實(shí)現(xiàn)限流的方法,其中最常用的是 RateLimiter
類。RateLimiter
可以幫助我們控制應(yīng)用程序的資源消耗速度,例如限制每秒的請(qǐng)求數(shù)量。
實(shí)現(xiàn)
1.依賴引入:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
2.application.yml 中配置限流參數(shù):
rate-limiter:
permits-per-second: 5 # 每秒許可數(shù)
warmup-period: 0 # 預(yù)熱時(shí)間(秒)
timeout: 0 # 獲取許可的超時(shí)時(shí)間(秒)
3.限流配置屬性類
@Data
@Component
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {
/**
* 每秒許可數(shù)
*/
private double permitsPerSecond;
/**
* 預(yù)熱時(shí)間(秒)
*/
private long warmupPeriod;
/**
* 獲取許可的超時(shí)時(shí)間(秒)
*/
private long timeout;
}
4.配置 RateLimiter
@Configuration
public class RateLimiterConfig {
/**
* 配置 RateLimiter Bean
*
* @param properties 注入的限流配置屬性
* @return RateLimiter 實(shí)例
*/
@Bean
public RateLimiter rateLimiter(RateLimiterProperties properties) {
if (properties.getWarmupPeriod() > 0) {
// 創(chuàng)建帶有預(yù)熱期的 RateLimiter
return RateLimiter.create(
properties.getPermitsPerSecond(),
properties.getWarmupPeriod(),
TimeUnit.SECONDS
);
} else {
// 創(chuàng)建標(biāo)準(zhǔn)的 RateLimiter
return RateLimiter.create(properties.getPermitsPerSecond());
}
}
}
5.創(chuàng)建控制器
@RestController
public class RateLimiterController {
@Autowired
private RateLimiter rateLimiter;
@Autowired
private RateLimiterProperties properties;
/**
* 測(cè)試限流接口
*
* @return 請(qǐng)求結(jié)果
*/
@GetMapping("/api/test")
//@RateLimitAspect(qps = 2, timeout = 200, timeUnit = TimeUnit.MILLISECONDS)
public ResponseEntity<String> rateApi() {
boolean acquired = rateLimiter.tryAcquire(properties.getTimeout(), TimeUnit.SECONDS);
if (acquired) {
// 允許請(qǐng)求,返回成功響應(yīng)
return ResponseEntity.ok("請(qǐng)求成功!");
} else {
// 拒絕請(qǐng)求,返回限流響應(yīng)
return ResponseEntity.status(429).body("請(qǐng)求過多,請(qǐng)稍后再試!");
}
}
}
6.進(jìn)階版利用切面:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimitAspect {
double qps() default 1; // 每秒鐘生成令牌的速率
long timeout() default 0; // 嘗試獲取令牌的超時(shí)時(shí)間
TimeUnit timeUnit() default TimeUnit.SECONDS; // 超時(shí)時(shí)間單位
}
@Aspect
@Component
public class ApiRateLimitAspect {
private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
@Before("@annotation(RateLimitAspect)")
public void limit(JoinPoint joinPoint, RateLimitAspect rateLimitAspect) {
String methodName = joinPoint.getSignature().toLongString();
double qps = rateLimitAspect.qps();
RateLimiter limiter = rateLimiters.computeIfAbsent(methodName, k -> RateLimiter.create(qps));
long timeout = rateLimitAspect.timeout();
TimeUnit timeUnit = rateLimitAspect.timeUnit();
if (timeout > 0) {
if (!limiter.tryAcquire(timeout, timeUnit)) {
throw new RuntimeException("API rate limit exceeded");
}
} else {
if (!limiter.tryAcquire()) {
throw new RuntimeException("API rate limit exceeded");
}
}
}
}