当前位置: 首页 > article >正文

轻松实现 API 接口限流:Bucket4j 在 Spring Boot 中的应用

一、简介

在当今的数字化时代,访问速率限制成为了保护应用程序接口(API)的重要策略。它能够有效防止无意或恶意的过度使用,确保 API 的稳定运行和安全性。

访问速率限制是对 API 访问进行约束的一种方式,限定客户端在特定时间内调用 API 的次数。通常,速率限制通过跟踪 IP 地址、API 密钥或访问令牌等方式应用于 API。作为 API 开发人员,当客户端达到限制时,我们有多种应对策略。其中,请求排队直至剩余时间结束是最为常用的方式,另外还可以直接拒绝请求(返回 HTTP 429 请求过多状态码)。

本文将为大家介绍一款强大的开源限流组件——Bucket4j。它基于令牌桶算法,提供了强大的限流功能,既可用于独立的 JVM 应用程序,也可用于集群环境。同时,通过 JCache(JSR107)规范,Bucket4j 还支持内存或分布式缓存。

令牌桶算法形象地假设我们有一个“桶”,其容量被定义为可容纳的令牌数量。每当消费者想要访问 API 端点时,就必须从桶中获取一个令牌。如果桶中有令牌,我们就会从桶中移除令牌,并接受请求;反之,如果桶中没有令牌,我们就会拒绝请求。在请求消耗令牌的同时,我们也在以某种固定的速度补充令牌。

例如,对于一个速率限制为每分钟 100 个请求的应用程序接口,我们可以创建一个容量为 100 的水桶,每分钟填充 100 个令牌。如果在一分钟内收到 70 个请求,少于可用令牌的数量,那么在下一分钟开始时,我们只需再添加 30 个令牌,就能使水桶达到容量。另一方面,如果在 40 秒内用完了所有令牌,我们将等待 20 秒来重新装满令牌桶。

接下来,我们将详细介绍在 Spring Boot 中如何使用 Bucket4j 实现限流。

二、实战案例

  1. 环境准备
    • 引入依赖
<dependency>
  <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>

  <artifactId>bucket4j-spring-boot-starter</artifactId>

  <version>0.12.7</version>

</dependency>

<dependency>
  <groupId>com.bucket4j</groupId>

  <artifactId>bucket4j-redis</artifactId>

  <version>8.10.1</version>

</dependency>

<dependency>
  <groupId>redis.clients</groupId>

  <artifactId>jedis</artifactId>

</dependency>

<dependency>
  <groupId>io.micrometer</groupId>

  <artifactId>micrometer-core</artifactId>

</dependency>

接下来的案例是基于 redis 的,所以引入了 jedis。你也可以选择 lettuce 或者 redisson,但这两个貌似需要是 webflux 环境。

- **jedis 配置**:
@Bean
public JedisPool jedisPool(
    @Value("${spring.data.redis.port}") Integer port,
    @Value("${spring.data.redis.host}") String host,
    @Value("${spring.data.redis.password}") String password,
    @Value("${spring.data.redis.database}") Integer database
  ) {
  // buildPoolConfig 方法自己进行配置吧
  final JedisPoolConfig poolConfig = buildPoolConfig();
  return new JedisPool(poolConfig, host, port, 60000, password, database);
}

以上基础环境准备就绪后,就可以进行规则配置了。规则配置可以基于两种方式,即基于配置文件和基于注解(AOP)。

- **定义接口**:
@RestController
@RequestMapping("/products")
public class ProductController {

  @GetMapping("/{id}")
  public Product getProduct(@PathVariable Integer id) {
    return new Product(id, "商品 - " + id, BigDecimal.valueOf(new Random().nextDouble(1000)));
  }
}

接下来将基于上面的接口进行限流配置。

  1. 基于配置文件

以上是基于配置文件规则的应用,它还有很多其它的配置属性,详细内容可查看官方文档github.com/MarcGiffing…。接下来介绍基于注解的方式。

- 基于配置文件的规则配置底层实现是通过 Filter。
bucket4j:
  cache-to-use: redis-jedis
  filter-config-caching-enabled: true
  filters:
  - cache-name: product_cache_name
    id: product_filter
    # 配置请求 url 的规则;这里底层是通过正则表达式进行匹配的
    url: /products/.*
    rate-limits:
    - 
      #这里的 cache-key 非常关键;用于区分不同请求的情况;
      #比如,这里我会根据不同的请求 id 来限制访问速率
      #这里可以写 spel 表达式,这里调用的是 HttpServletRequest#getParameter 方法
      cache-key: getParameter("id")
      bandwidths:
      #配置桶的容量
      - capacity: 2
        # 时间
        time: 30
        # 单位
        unit: seconds
        # 填充速度;这会每隔 30 秒进行填充
        refill-speed: interval
- 修改默认的限流提示:
bucket4j:
  filters:
  - cache-name: product_cache_name
    http-content-type: 'application/json;charset=utf-8'
    http-response-body: '{"code": -1, "message": "请求太快了"}'

注意:必须同时设置 content-type 和字符编码,否则会出现乱码。

- 条件放行:
bucket4j:
  filters:
  - cache-name: product_cache_name
    rate-limits:
    - 
      skip-condition: 'getParameter("id").equals("6")'

当请求 id 的值为 6 时则跳过规则,直接放行。

  1. 基于注解
    • 通过“@RateLimiting”注解,AOP 可以拦截目标方法。这样,你就可以全面访问方法参数,轻松定义速率限制键或有条件地跳过速率限制。
    • 配置文件中配置规则:
bucket4j:
  methods:
  - name: storage_rate #在代码中会通过该名称引用
    cache-name: storage_cache_name
    rate-limit:
      bandwidths:
      - capacity: 2
        time: 30
        unit: seconds
        refill-speed: interval
- 接口注解,配置限流:
@RateLimiting(
    name = "storage_rate", 
    cacheKey = "'storage-' + #id",
    skipCondition = "#name eq 'admin'",
    ratePerMethod = true,
    fallbackMethodName = "getStorageFallback"
  )
@GetMapping("/{id}")
public R<Storage> getStorage(@PathVariable Integer id, String name) {
  return R.success(new Storage(id, "SP001 - " + id, new Random().nextInt(10000)));
}
// 回退方法的签名必须与业务方法一致
public R<Storage> getStorageFallback(Integer id, String name) {
  return R.failure(String.format("请求 id=%d,name=%s 被限流", id, name));
}

“skipCondition”属性定义了如果请求的 name 的值为“admin”则跳过,不限流。

- “@RateLimiting”注解还可以应用到类中,这样该类中的所有方法都会被限流,如下示例:
@Service
@RateLimiting(
    name = "storage_rate", 
    cacheKey = "getName",
    ratePerMethod = false
  )
public class StorageService {

  public Storage queryStorageById(Integer id) {
    return new Storage(id, "SP001 - " + id, new Random().nextInt(10000));
  }
  
  @IgnoreRateLimiting
  public List<Storage> queryStorages() {
    return List.of(
        new Storage(1, "SP001 - " + 1, new Random().nextInt(10000)),
        new Storage(2, "SP002 - " + 2, new Random().nextInt(10000)),
        new Storage(3, "SP003 - " + 3, new Random().nextInt(10000))
      );
  }
}

上面代码中,“queryStorageById”方法会被限流,而“queryStorages”方法被“@IgnoreRateLimiting”注解标注,所以不会被限流。


http://www.kler.cn/news/355632.html

相关文章:

  • 自适应权重
  • MongoDB集合(Collection)的详细使用说明
  • OpenAI重磅发布GPT-4O-Audio-Preview 语音也能“读懂”情绪!
  • 重塑企业数字化未来:物联网与微服务架构的战略性深度融合
  • 【设计一个恒流转恒压用于电池充电管理】2022-01-25
  • 判断推理学习
  • React Native 项目中使用 Expo Application Services (EAS) 进行多渠道打包
  • 分享一套SpringBoot+Vue民宿(预约)系统
  • Python画笔案例-087 绘制 旋转的文字
  • 人脸识别系统-特征算法
  • C++ 数组、递归两种方式实现二分查找
  • YOLOv8/YOLOv11使用web界面推理自己的模型,Gradio框架快速搭建
  • C++ 递归函数之分解质因子
  • Neuromnia是一家创新的AI平台用Llama为自闭症护理领域带来全新解决方案
  • 机器学习—基于随机森林的贷款可能性预测系统实现
  • 将 Ubuntu 系统中的 **swap** 空间从 2GB 扩展到 16GB
  • linux搭建elasticsearch
  • 分布式环境下验证码登录的技术实现
  • 2021-04-14 proteus中仿真时74HC245三态双向端口扩展输出
  • Java集合(3:Set和Map)