Bucket4j 限流入门教程之一

前言

限流概述

在开发高并发系统时可以用三把利器来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统处理的容量,是抗高并发流量的 “银弹”;而降级是当服务出现问题或者影响到核心流程时,需要暂时将其屏蔽掉,待高峰过去之后或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询等,因此需要有一种手段来限制这些场景的并发 / 请求量,即限流。限流的目的是通过对并发访问 / 请求进行限速或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或友好的展示页)、排队或等待(比如秒杀、评论、下单等场景)、降级(返回兜底数据或默认数据)。主流的中间件都会有单机限流框架,一般支持两种限流模式:控制速率和控制并发。Spring Cloud Zuul 通过第三方扩展 spring-cloud-zuul-ratelimit 也可以支持限流,而 Spring Cloud Gateway 的限流实现可以看这里。常见的限流算法有漏桶和令牌桶,计数器也可以进行粗暴限流实现。对于限流算法,可以参考 Guava 中的 RateLimiter、Bucket4jRateLimitJ 等项目的具体实现。

Bucket4j 介绍

Bucket4j 是基于令牌桶算法的 Java 限流库,它主要用在 3 种场景:

  • 限制比较重工作的速率
  • 限制对 API 的访问速率
  • 将限流作为定时器,例如有些场景限制你对服务提供方的调用速度,因此使用限流器作为定时器,定时按照约定速率调用服务提供方

Spring Boot 整合 Bucket4j

本案例主要是简单演示如何在单机 Spring Boot 应用中使用 Bucket4j,使用的缓存组件是 Caffeine(JCache API),点击下载完整的案例代码。特别注意,在企业开发中,若 Spring Boot 应用是以集群的方式部署,则必须采用分布式缓存方案,具体的参考方案如下:

Back-endDocumentation pageAsync supportedOptimized serializationThin-client support
Hazelcastbucket4j-hazelcastYesYesPlanned
Apache Ignitebucket4j-igniteYesn/aYes
Inifinispanbucket4j-infinspanYesYesNo
Oracle Coherencebucket4j-coherenceYesYesNo

代码示例

添加 Bucket4j + Spring Boot Starter + Caffeine 相关的 Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>jcache</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>

创建缓存配置类,并添加 @EnableCaching 注解来启用缓存功能

1
2
3
4
5
@EnableCaching
@Configuration
public class Bucket4jCacheConfig {

}

创建 Controller 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

@GetMapping("hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("Hello World");
}

@GetMapping("world")
public ResponseEntity<String> world() {
return ResponseEntity.ok("Hello World");
}

}

创建主启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Bucket4jApplication {

public static void main(String[] args) {
SpringApplication.run(Bucket4jApplication.class, args);
}

}

创建 application.yml 主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server:
port: 8080

spring:
application:
name: bucket4j-spring-boot-caffeine
cache:
cache-names:
- buckets
caffeine:
spec: maximumSize=1000000,expireAfterAccess=3600s # 设置了名为buckets的缓存,过期时间为1h,容量为1000000

bucket4j:
enabled: true
filters:
- cache-name: buckets
url: .*
rate-limits:
- bandwidths:
- capacity: 2
time: 10
unit: seconds # 设置的rate-limits每10秒2个token

Bucket4j 配置参数说明如下:

  • bucket4j.enabled=true:启用 Bucket4j 的自动配置
  • bucket4j.filters.cache-name:从缓存中获取 API 密钥的 Bucket
  • bucket4j.filters.url:表示应用速率限制的路径表达式,.* 表示拦截所有 URL
  • bucket4j.filters.rate-limits.bandwidths:定义 Bucket4j 速率限制参数

代码测试

正常请求接口的时候,服务端响应的结果如下:

1
2
3
4
5
$ curl -v -X GET 'http://127.0.0.1:8080/hello'

< HTTP/1.1 200
< X-Rate-Limit-Remaining: 1
Hello World

当频繁请求接口的时候,服务端会返回 429 的 HTTP 状态码和 JSON 数据 { "message": "Too many requests!" },如下所示:

1
2
3
4
5
$ curl -v -X GET 'http://127.0.0.1:8080/hello'

< HTTP/1.1 429
< X-Rate-Limit-Retry-After-Seconds: 2
{ "message": "Too many requests!" }

自定义限流响应结果

当触发限流条件时,Bucket4j 默认会返回 429 的 HTTP 状态码,同时返回 JSON 数据 { "message": "Too many requests!" } 给客户端。若需要自定义返回的数据内容,可以配置 http-response-body 参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
bucket4j:
enabled: true
filters:
- cache-name: buckets
url: .*
http-response-body: '{"code":429,"data":"","msg":"Too many requests!"}'
rate-limits:
- bandwidths:
- capacity: 2
time: 10
unit: seconds

参考博客