1. 引言:限流背景與 Bucket4j 項目概述
在微服務與高併發系統中,合理地限制請求速率能夠保護後端服務不被洪水般的請求壓垮,平滑流量並保障系統可用性。Bucket4j 是一個基於 Java 的令牌桶(Token Bucket)限流庫,支持內存與多種分佈式存儲後端(如 Redis、Hazelcast、JCache 等),並且提供豐富的 API,讓開發者能靈活定義限流策略與集成方式。
Bucket4j 的核心優勢在於:
- 純 Java 實現:無需引入複雜的代理或外部依賴
- 靈活的令牌桶配置:支持定速、突發模式、帶時間窗口的限流
- 分佈式後端支持:基於 Redis、Hazelcast、Caffeine 等實現共享限流
- 同步 & 異步 API:滿足不同場景下對性能的要求
2. 核心概念:令牌桶算法與 Bucket4j 組件
- 令牌桶(Token Bucket):維護一個“桶”,桶中存放令牌,令牌按固定速率生成。當請求到來時,需要消耗一定數量的令牌,若桶空則拒絕請求或等待。
- Bandwidth:代表一個限流規則,包括容量(maxTokens)、填充速率(refillTokens/refillPeriod)等。
- Bucket:對應一個具體的令牌桶實例,由一個或多個 Bandwidth 組成。
- 時間度量器(TimeMeter):Bucket4j 內部使用,可替換為自定義實現以適配不同環境。
3. 快速入門:Maven 依賴與基礎配置
在 Maven 項目中引入核心依賴:
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.3.0</version>
</dependency>
若使用 Redis 後端還需引入:
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-redis-extension</artifactId>
<version>8.3.0</version>
</dependency>
4. 基本用法:創建桶、消費令牌、檢查剩餘容量
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import java.time.Duration;
public class RateLimiterDemo {
public static void main(String[] args) {
// 定義限流規則:每秒生成 10 個令牌,桶容量最大為 20
Bandwidth limit = Bandwidth.simple(10, Duration.ofSeconds(1))
.withInitialTokens(20);
// 創建令牌桶
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
// 嘗試消費令牌
if (bucket.tryConsume(1)) {
// 成功消費一個令牌,允許請求
System.out.println("請求允許,剩餘令牌:" + bucket.getAvailableTokens());
} else {
// 消費失敗,限流
System.out.println("請求被限流!");
}
}
}
- tryConsume(n):立即嘗試消耗 n 個令牌,返回布爾結果。
- consume(n):不足時阻塞等待。
5. 高級功能:多種填充策略、異步模式、分佈式存儲
- 多個 Bandwidth 組合
Bucket bucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(5, Duration.ofSeconds(1))) // 短期限流
.addLimit(Bandwidth.simple(50, Duration.ofMinutes(1))) // 長期限流
.build();
- 異步 API
bucket.asAsync().tryConsume(1)
.thenAccept(consumed -> {
if (consumed) System.out.println("異步限流通過");
else System.out.println("異步限流拒絕");
});
- 分佈式後端示例(Redis)
import io.github.bucket4j.redis.RedisBucketBuilder;
import io.github.bucket4j.redis.redisson.cas.RedissonBasedProxyManager;
import org.redisson.api.RedissonClient;
RedissonClient client = ...; // 注入或創建 RedissonClient
RedissonBasedProxyManager<String> proxyManager = new RedissonBasedProxyManager<>(client);
Bucket bucket = proxyManager.builderForKey("user:1234:bucket")
.withDefaultBandwidth(Bandwidth.simple(10, Duration.ofSeconds(1)))
.build();
通過分佈式代理管理器,可以在多實例環境中共享同一令牌桶,實現全侷限流。
6. 與 Spring Boot 集成:過濾器與註解實現
方式一:Servlet 過濾器
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Bucket bucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(100, Duration.ofMinutes(1)))
.build();
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
if (bucket.tryConsume(1)) {
chain.doFilter(req, res);
} else {
res.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
res.getWriter().write("Too many requests");
}
}
}
方式二:自定義註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int tokens() default 1;
long replenishRate() default 10;
long periodSeconds() default 1;
}
@Aspect
@Component
public class RateLimitAspect {
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
Bucket bucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(rateLimit.replenishRate(), Duration.ofSeconds(rateLimit.periodSeconds())))
.build();
if (bucket.tryConsume(rateLimit.tokens())) {
return pjp.proceed();
} else {
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS, "請求頻率過高");
}
}
}
7. 性能與注意事項
- 線程安全:Bucket4j 的核心實現是線程安全的,單機模式下性能開銷極低。
- 內存 vs. 分佈式:本地桶速度最快;若需要多實例共享狀態,可使用 Redis/Hazelcast 後端。
- 狀態持久化:分佈式後端需注意網絡延遲與故障切換策略。
- 容量計算:合理設置 initialTokens 與 bandwidth,避免過度積累或瞬時突發耗盡。
8. 應用場景
- API 網關限流:保護下游服務,防止惡意或意外的高併發。
- 登錄接口防刷:限制同一用户/IP 的請求頻率。
- 批量任務節流:控制批量處理任務發送速率,平滑資源使用。
9. 與其他限流庫的比較
Bucket4j 與其他常見的限流庫(如 Guava RateLimiter 和 Resilience4j)相比,具有以下優勢:
- 靈活性:支持多種限流算法和動態配置。
- 分佈式支持:原生支持分佈式環境,適合微服務架構。
- 高性能:專為高併發場景設計,延遲低、吞吐量高。
- 易用性:API 簡潔,學習曲線平緩。
| 特性 | Bucket4j | Guava RateLimiter | Resilience4j |
|---|---|---|---|
| 分佈式支持 | 是 | 否 | 否 |
| 動態配置 | 是 | 否 | 是 |
| 多種算法支持 | 是 | 否 | 是 |
| 易於集成 | 高 | 中 | 高 |
10. 完整的 Spring Boot 限流 Starter
rate-limiter-spring-boot-starter/
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── ratelimiter
│ │ │ ├── starter
│ │ │ │ ├── RateLimiterAutoConfiguration.java
│ │ │ │ ├── RateLimit.java
│ │ │ │ ├── RateLimitAspect.java
│ │ │ │ └── RateLimitProperties.java
│ │ └── resources
│ │ ├── META-INF
│ │ │ └── spring.factories
│ │ └── application.yml
└── example-usage
└── src
└── main
└── java
└── com
└── example
└── demo
├── DemoApplication.java
└── DemoController.java
10.1. pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>rate-limiter-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
10.2. RateLimitProperties.java
package com.example.ratelimiter.starter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "ratelimiter")
public class RateLimitProperties {
private long replenishRate = 10;
private Duration period = Duration.ofSeconds(1);
private long capacity = 20;
// getters & setters
}
10.3. RateLimit.java (註解)
package com.example.ratelimiter.starter;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
long capacity() default -1;
long replenishRate() default -1;
String period() default ""; // ISO-8601 duration
int tokens() default 1;
}
10.4. RateLimitAspect.java
package com.example.ratelimiter.starter;
import io.github.bucket4j.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@Aspect
@Component
@ConditionalOnProperty(prefix = "ratelimiter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class RateLimitAspect {
private final RateLimitProperties properties;
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
public RateLimitAspect(RateLimitProperties properties) {
this.properties = properties;
}
@Around("@annotation(com.example.ratelimiter.starter.RateLimit) || @within(com.example.ratelimiter.starter.RateLimit)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
RateLimit annotation = AnnotationUtils.findAnnotation(
pjp.getSignature().getDeclaringType(), RateLimit.class);
if (annotation == null) {
annotation = AnnotationUtils.findAnnotation(
((org.aspectj.lang.reflect.MethodSignature) pjp.getSignature()).getMethod(), RateLimit.class);
}
String key = pjp.getSignature().toShortString();
Bucket bucket = cache.computeIfAbsent(key, k -> buildBucket(annotation));
if (bucket.tryConsume(getTokens(annotation))) {
return pjp.proceed();
}
throw new IllegalStateException("Too many requests");
}
private Bucket buildBucket(RateLimit rl) {
long capacity = rl.capacity() > 0 ? rl.capacity() : properties.getCapacity();
long replenish = rl.replenishRate() > 0 ? rl.replenishRate() : properties.getReplenishRate();
Duration period = !rl.period().isEmpty() ? Duration.parse(rl.period()) : properties.getPeriod();
Bandwidth limit = Bandwidth.classic(capacity, Refill.greedy(replenish, period));
return Bucket4j.builder().addLimit(limit).build();
}
private int getTokens(RateLimit rl) {
return rl.tokens();
}
}
10.5. RateLimiterAutoConfiguration.java
package com.example.ratelimiter.starter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(Bucket4j.class)
@EnableConfigurationProperties(RateLimitProperties.class)
@ConditionalOnProperty(prefix = "ratelimiter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class RateLimiterAutoConfiguration {
// 自動裝配切面和屬性
}
10.6. spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.ratelimiter.starter.RateLimiterAutoConfiguration
10.7. application.yml (Starter 默認配置)
ratelimiter:
enabled: true
replenishRate: 5
period: PT1S
capacity: 10
10.8.示例項目 (example-usage)
DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
DemoController.java
package com.example.demo;
import com.example.ratelimiter.starter.RateLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/hello")
@RateLimit(tokens = 1, capacity = 3, replenishRate = 1, period = "PT1S")
public String hello() {
return "Hello, world!";
}
}
上述腳手架包含了一個可發佈為 Spring Boot Starter 的完整示例:
- pom.xml:定義了核心依賴(Spring Boot、Bucket4j、AspectJ 和自動配置)。
- RateLimitProperties:用於讀取 application.yml 中的限流參數。
- @RateLimit 註解:可標註在類或方法上,靈活覆蓋全局屬性。
- RateLimitAspect:通過 AOP 攔截帶註解的請求,基於 Bucket4j 實現令牌桶限流。
- RateLimiterAutoConfiguration + spring.factories:實現自動裝配。
- 默認配置 (application.yml):提供全侷限流開關與默認參數。
- 示例項目 (example-usage):展示在 Spring Boot 應用中如何引入並使用 Starter。
可以將 spring-boot-rate-limiter-starter 安裝到私服或本地倉庫,並在任何 Spring Boot 項目中添加依賴:
<dependency>
<groupId>com.example</groupId>
<artifactId>rate-limiter-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然後在控制器方法上使用 @RateLimit 即可輕鬆接入限流功能。
11.發佈
11.1. 發佈到 Maven 本地倉庫
在 rate-limiter-spring-boot-starter 根目錄下執行:
mvn clean install
11.2. 發佈到私服
1. 在 pom.xml 中添加 distributionManagement
<distributionManagement>
<repository>
<id>private-repo</id>
<url>https://nexus.example.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>private-snapshots</id>
<url>https://nexus.example.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
2. 在 maven/conf/settings.xml 中配置憑據
<servers>
<server>
<id>private-repo</id>
<username>deploy</username>
<password>您的密碼</password>
</server>
<server>
<id>private-snapshots</id>
<username>deploy</username>
<password>您的密碼</password>
</server>
</servers>
3. 執行發佈
mvn clean deploy
– 將 release 和 snapshot 同步到私服對應倉庫。
11.3. 在應用中添加依賴
在 example-usage/pom.xml 中,添加:
<repositories>
<repository>
<id>private-repo</id>
<url>https://nexus.example.com/repository/maven-releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>rate-limiter-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version> <!-- 或發佈後的正式版本 -->
</dependency>
</dependencies>
然後重新啓動示例應用,@RateLimit 即可生效。
12. 小結與參考鏈接
Bucket4j 提供了一個靈活、可擴展且性能優秀的 Java 限流方案。通過豐富的 API 與多種後端適配,能夠滿足從單機到分佈式、多速率到混合限流等多種需求。
- 官方倉庫:https://github.com/bucket4j/bucket4j
- 官方網站:https://bucket4j.com
參考: 原文出處:https://blog.csdn.net/yangshangwei/article/details/149697329