SpringBoot - Bucket4j:分布式限流的“流速调节器”

在这里插入图片描述


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 后端。
  • 状态持久化:分布式后端需注意网络延迟与故障切换策略。
  • 容量计算:合理设置 initialTokensbandwidth,避免过度积累或瞬时突发耗尽。

8. 应用场景

  • API 网关限流:保护下游服务,防止恶意或意外的高并发。
  • 登录接口防刷:限制同一用户/IP 的请求频率。
  • 批量任务节流:控制批量处理任务发送速率,平滑资源使用。

9. 与其他限流库的比较

Bucket4j 与其他常见的限流库(如 Guava RateLimiter 和 Resilience4j)相比,具有以下优势:

  • 灵活性:支持多种限流算法和动态配置。
  • 分布式支持:原生支持分布式环境,适合微服务架构。
  • 高性能:专为高并发场景设计,延迟低、吞吐量高。
  • 易用性:API 简洁,学习曲线平缓。
特性Bucket4jGuava RateLimiterResilience4j
分布式支持
动态配置
多种算法支持
易于集成

10. 完整的 Spring Boot 限流 Starter

spring-boot-rate-limiter-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

1. pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-rate-limiter-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>

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
}

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;
}

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();
    }
}

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 {
    // 自动装配切面和属性
}

6. spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.ratelimiter.starter.RateLimiterAutoConfiguration

7. application.yml (Starter 默认配置)

ratelimiter:
  enabled: true
  replenishRate: 5
  period: PT1S
  capacity: 10

示例项目 (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 的完整示例:

  1. pom.xml:定义了核心依赖(Spring Boot、Bucket4j、AspectJ 和自动配置)。
  2. RateLimitProperties:用于读取 application.yml 中的限流参数。
  3. @RateLimit 注解:可标注在类或方法上,灵活覆盖全局属性。
  4. RateLimitAspect:通过 AOP 拦截带注解的请求,基于 Bucket4j 实现令牌桶限流。
  5. RateLimiterAutoConfiguration + spring.factories:实现自动装配。
  6. 默认配置 (application.yml):提供全局限流开关与默认参数。
  7. 示例项目 (example-usage):展示在 Spring Boot 应用中如何引入并使用 Starter。

可以将 spring-boot-rate-limiter-starter 安装到私服或本地仓库,并在任何 Spring Boot 项目中添加依赖:

<dependency>
  <groupId>com.example</groupId>
  <artifactId>spring-boot-rate-limiter-starter</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

然后在控制器方法上使用 @RateLimit 即可轻松接入限流功能。


发布

发布到 Maven 本地仓库

spring-boot-rate-limiter-starter 根目录下执行:

mvn clean install
  • 作用:将 0.0.1-SNAPSHOT 版本安装到 ~/.m2/repository/com/example/spring-boot-rate-limiter-starter/

发布到私服

  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. ~/.m2/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 同步到私服对应仓库。


在应用中添加依赖

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>spring-boot-rate-limiter-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version> <!-- 或发布后的正式版本 -->
  </dependency>
</dependencies>

然后重新启动示例应用,@RateLimit 即可生效。


11. 小结与参考链接

Bucket4j 提供了一个灵活、可扩展且性能优秀的 Java 限流方案。通过丰富的 API 与多种后端适配,能够满足从单机到分布式、多速率到混合限流等多种需求。


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值