2
2

More than 3 years have passed since last update.

bucket4j を用いた Rate-Limiting

Last updated at Posted at 2020-04-21

bucket4j は Java 向けの Rate-Limiting ライブラリとしては著名なものになり、さまざまな OSS、ライブラリでも用いられている。
内部的にはトラフィックシェーピングなどで広く用いられる Token Bucket と呼ばれるアルゴリズムを用いている。

Spring Boot Integration

3rd Party 製ではあるが Spring Boot Starter についても提供されている。

この記事では bucket4j-spring-boot-starter をベースに簡単なアプリケーションを動作させ、実際にどのように動作するか検証を行った。

bucket4j-spring-boot-starter-examples

bucket4j-spring-boot-starter の作者が、動作確認用の examples プロジェクトをいくつか公開しているため、この記事ではこちらをベースに動作を試みる。

いくつかある中で、 jCache(EHCache)を状態管理に用いた bucket4j-spring-boot-starter-example-ehcache が最も単体で動作をさせやすいので、こちらを用いる。

Configuration

上記の examples ファイルを checkout し、いくつか設定を変更する。
なお、今回は 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>

application.yml

filter-key-type の定義を削除

最新版では deprecated 扱いになっているため。以下の定義を削除。

設定パラメータの微調整。

対象となる 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)

Running

実際に curl コマンドで Rate-Limiting 対象の URL Endpoint にリクエストを送り、挙動を確認する。
同一ユーザーで1分間に5回以上リクエストをすると、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.)

Rate-Limit 時 (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.)

Monitoring

Spring Actuator 経由で設定状況、ならびに Rate-Limiting 処理のステータスについて確認をすることができる。

/actuator/bucket4j

{
  "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!\" }"
    }
  ]
}

/actuator/prometheus

# 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

Conclusion

bucket4j ならびに bucket4j-spring-boot-starter を用いると、かなり簡単に既存の Spring Boot アプリケーションに Rate-Limiting の機能を追加することができる。

本格的に Rate-Limiting の処理をシステム全体に適用する場合は、Ambassador Pattern や Side-Car Pattern を用いて API Gateway のミドルウェアを導入した方が望ましいと思います。

特定のアプリケーションの処理に個別に適用する程度であれば、bucket4j などを用いて簡易的に導入するというのも、一つの選択肢になると思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2