前言
限流概述
在开发高并发系统时可以用三把利器来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统处理的容量,是抗高并发流量的 “银弹”;而降级是当服务出现问题或者影响到核心流程时,需要暂时将其屏蔽掉,待高峰过去之后或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询等,因此需要有一种手段来限制这些场景的并发 / 请求量,即限流。限流的目的是通过对并发访问 / 请求进行限速或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或友好的展示页)、排队或等待(比如秒杀、评论、下单等场景)、降级(返回兜底数据或默认数据)。主流的中间件都会有单机限流框架,一般支持两种限流模式:控制速率和控制并发。Spring Cloud Zuul 通过第三方扩展 spring-cloud-zuul-ratelimit 也可以支持限流,而 Spring Cloud Gateway 的限流实现可以看这里。常见的限流算法有漏桶和令牌桶,计数器也可以进行粗暴限流实现。对于限流算法,可以参考 Guava 中的 RateLimiter、Bucket4j、RateLimitJ 等项目的具体实现。
Bucket4j 介绍
Bucket4j 是基于令牌桶算法的 Java 限流库,它主要用在 3 种场景:
- 限制比较重工作的速率
- 限制对 API 的访问速率
- 将限流作为定时器,例如有些场景限制你对服务提供方的调用速度,因此使用限流器作为定时器,定时按照约定速率调用服务提供方
Spring Boot 整合 Bucket4j
本案例主要是简单演示如何在单机 Spring Boot 应用中使用 Bucket4j,使用的缓存组件是 Caffeine(JCache API),点击下载完整的案例代码。特别注意,在企业开发中,若 Spring Boot 应用是以集群的方式部署,则必须采用分布式缓存方案,具体的参考方案如下:
代码示例
添加 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
bucket4j: enabled: true filters: - cache-name: buckets url: .* rate-limits: - bandwidths: - capacity: 2 time: 10 unit: seconds
|
Bucket4j 配置参数说明如下:
bucket4j.enabled=true
:启用 Bucket4j 的自动配置bucket4j.filters.cache-name
:从缓存中获取 API 密钥的 Bucketbucket4j.filters.url
:表示应用速率限制的路径表达式,.*
表示拦截所有 URLbucket4j.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
|
参考博客