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