使用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的简易解决方案也是一个选择。