使用Bucket4j进行速率限制

bucket4j 是一個在 Java 中著名的速率限制庫,並且被應用在許多開源軟體和函式庫中。
在內部它使用了被廣泛使用的流量整形演算法,稱為令牌桶算法。

Spring Boot 整合

虽然是第三方制作的,但也提供了Spring Boot Starter。

本文使用 bucket4j-spring-boot-starter 作为基础,运行简单应用程序,并验证其实际运行情况。

桶4j-spring-boot-starter-examples

由于 bucket4j-spring-boot-starter 的作者发布了一些用于验证功能的示例项目,因此本文将以此为基础尝试进行操作。

在几个选择中,由于 bucket4j-spring-boot-starter-example-ehcache 使用 jCache(EHCache) 进行状态管理,因此它是最容易独立运行的选项。因此,我们将选择使用它。

配置

请检出上述的示例文件并进行一些设置更改。
请注意,本次假设在Java 11上运行。

pom.xml 可以进行重写

对原始的 bucket4j-spring-boot-starter 进行以下修改

使用bucket4j-spring-boot-starter的最新版本。

<dependency>
        <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
        <artifactId>bucket4j-spring-boot-starter</artifactId>
        <version>0.2.0</version>
</dependency>

JAXB 兼容性

由于Java9及其之后的版本不支持标准兼容性,因此需要添加库。

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.3.0</version>
</dependency>

javax.activation的支持

由于Java9后javax.activation不再标准支持,所以需要添加库。

<dependency>
    <groupId>com.sun.activation</groupId>
    <artifactId>javax.activation</artifactId>
    <version>1.2.0</version>
</dependency>

应用配置文件.yml

删除过滤关键字类型的定义

由于在最新版中被标记为“已弃用”,因此删除以下定义。

微调设置参数。

改变了目标URL等。
结果,配置文件将会变成以下的形式。

management:
  endpoints:
    web:
      exposure:
        include: "*"
  prometheus:
    enabled: true
spring:
  cache:
    jcache:
      config: classpath:ehcache.xml
bucket4j:
  enabled: true
  filters:
  - cache-name: buckets
    filter-method: servlet
    filter-order: -10
    url: /login
    metrics:
      tags:
      - key: USERNAME
        expression: "@securityService.username() != null ? @securityService.username() : 'anonym'"
      - key: URL
        expression: getRequestURI()
    rate-limits:
    - bandwidths:
      - capacity: 5
        time: 1
        unit: minutes
    • Rete-Limiting : 有効

 

    • 対象URL:/login

 

    • tags : username, url (tagの単位で rate-limiting の計算を行う)

 

    • 制限 : username x url ごとに1分あたり5回のリクエストを許可

 

    stateの管理 : jcache(ehcache)

奔跑

使用curl命令实际发送请求到受到速率限制的URL端点,以确认其行为。
当同一用户在一分钟内发送超过五次的请求时,将返回429状态码。

一般情况下 (200)

< HTTP/1.1 200
< X-Rate-Limit-Remaining: 0
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
(snip.)

速率限制达到 (429) 时


< HTTP/1.1 429
< X-Rate-Limit-Retry-After-Seconds: 60
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cacheo
< Expires: 0-
< X-Frame-Options: DENY
(snip.)

监控

你可以通过Spring Actuator来检查配置状态以及速率限制处理的状态。

/执行器/桶4j

{
  "servlet": [
    {
      "cacheName": "buckets",
      "filterMethod": "SERVLET",
      "strategy": "FIRST",
      "url": "/login",
      "filterOrder": -10,
      "rateLimits": [
        {
          "filterKeyType": null,
          "executeCondition": null,
          "skipCondition": null,
          "expression": "1",
          "bandwidths": [
            {
              "capacity": 5,
              "time": 1,
              "unit": "MINUTES",
              "fixedRefillInterval": 0,
              "fixedRefillIntervalUnit": "MINUTES"
            }
          ]
        }
      ],
      "metrics": {
        "enabled": true,
        "types": [
          "CONSUMED_COUNTER",
          "REJECTED_COUNTER"
        ],
        "tags": [
          {
            "key": "USERNAME",
            "expression": "@securityService.username() != null ? @securityService.username() : 'anonym'",
            "types": [
              "CONSUMED_COUNTER",
              "REJECTED_COUNTER"
            ]
          },
          {
            "key": "URL",
            "expression": "getRequestURI()",
            "types": [
              "CONSUMED_COUNTER",
              "REJECTED_COUNTER"
            ]
          }
        ]
      },
      "httpResponseBody": "{ \"message\": \"Too many requests!\" }"
    }
  ]
}

/执行器/普罗米修斯

# HELP bucket4j_summary_consumed_total 
# TYPE bucket4j_summary_consumed_total counter
bucket4j_summary_consumed_total{URL="/login",USERNAME="anonym",name="buckets",} 87.0
bucket4j_summary_consumed_total{URL="/login",USERNAME="admin",name="buckets",} 1.0
cache_removals{cache="buckets",cacheManager="cacheManager",name="buckets",} 0.0
cache_puts_total{cache="buckets",cacheManager="cacheManager",name="buckets",} 88.0
cache_evictions_total{cache="buckets",cacheManager="cacheManager",name="buckets",} 0.0
# HELP bucket4j_summary_rejected_total 
# TYPE bucket4j_summary_rejected_total counter
bucket4j_summary_rejected_total{URL="/login",USERNAME="anonym",name="buckets",} 796.0
bucket4j_summary_rejected_total{URL="/login",USERNAME="admin",name="buckets",} 7.0
cache_gets_total{cache="buckets",cacheManager="cacheManager",name="buckets",result="miss",} 2.0
cache_gets_total{cache="buckets",cacheManager="cacheManager",name="buckets",result="hit",} 890.0
http_server_requests_seconds_count{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 1.0
http_server_requests_seconds_sum{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 0.049276574
http_server_requests_seconds_max{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 0.049276574

结论

使用bucket4j和bucket4j-spring-boot-starter,可以很容易地将速率限制功能添加到现有的Spring Boot应用程序中。

如果要在整个系统中全面应用速率限制处理,我认为最好使用大使模式或侧车模式,并引入API网关中间件。

如果只是针对特定应用的处理进行个别应用,我认为使用类似于bucket4j的简易解决方案也是一个选择。

广告
将在 10 秒后关闭
bannerAds