在之前的博客分享中,我们介绍了在分布式场景下实现接口限流操作的重要性,尤其是在一些高并发的场景中通过限流操作可以有效的保护后端服务的安全。下面我们就来介绍一下在Spring Boot中通过Bucket4j来实现API限流。
什么是Bucket4j?
Bucket4j是一个高性能的Java限流库,基于令牌桶算法实现了限流功能。在之前我们也介绍过令牌桶算法的基本原理,如下所示。
令牌以固定的速率放入桶中,当用户请求到达时,必须从桶中取出令牌,带着令牌将请求发送到后端服务,如果桶中没有足够的令牌,那么请求就会被拒绝或者是进入等待机制中,从而实现了接口限流操作。
配置Bucket4j限流
在Spring Boot中使用Bucket4j进行接口限流,首先要做的事情就是引入相关的POM依赖,如下所示。
io.github.bucket4j
bucket4j-core
6.5.0
同时如果想要实现分布式环境下的限流操作,我们还需要引入Redis的Bucket4j扩展。如下所示。
io.github.bucket4j
bucket4j-redis
6.5.0
使用Bucket4j进行单机模式的限流
在单机场景中,Bucket4j会给予内存创建一个令牌通来实现流量的控制,我们可以通过Bucket对象结合Spring Boot的拦截器或者是通过过滤器来实现限流操作。
首先我们就需要先来配置一个限流的配置类,用来定义令牌桶、令牌生成速度等一些基础的属性操作,如下所示。我们定义了一个容量是10的桶,并且设置每秒10个令牌进入桶中。当然在实际场景中,可以根据自己的具体需求来调整限流操作。
@Configuration
public class RateLimitConfig {
// 创建一个令牌桶,设定桶的容量和令牌生成速率
@Bean
public Bucket bucket() {
Rate limit = Rate.of(10, Refill.greedy(10, java.time.Duration.ofSeconds(1))); // 每秒允许10个请求
return Bucket4j.builder()
.addLimit(limit)
.build();
}
}
配置好桶之后,接下来需要做的事情就是通过过滤器来实现限流的操作,通过这个过滤器操作会在请求进入到接口之前检查桶中是否有令牌,如果有则获取放行否则则进行拦截。
@Component
public class RateLimitFilter extends OncePerRequestFilter {
@Autowired
private Bucket bucket;
@Override
protected void doFilterInternal(HttpServletRequest request, javax.servlet.http.HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
// 令牌消耗成功,继续请求
filterChain.doFilter(request, response);
} else {
// 令牌桶已满或没有令牌,返回限流错误
response.setStatus(429);
response.getWriter().write("Too many requests. Please try again later.");
}
}
}
在每个请求到达时,检查是否有足够的令牌可供消耗。如果没有令牌,则返回HTTP 429状态码,并向客户端返回限流消息。
使用Redis进行分布式限流
Bucket4j也支持了基于Redis实现的分布式限流操作,可以进行多节点的部署,可以实现微服务场景中多实例之间的共享限流操作。
首先需要添加Redis的相关链接配置。
spring:
redis:
host: localhost
port: 6379
接下来,需要定义一个分布式限流操作类RateLimitConfig,用来定义Bucket4j的Redis扩展实现的分布式令牌桶,如下所示。
@Configuration
public class RateLimitConfig {
@Bean
public Bucket redisBucket(StringRedisTemplate redisTemplate) {
RedisBucketManager redisBucketManager = new RedisBucketManager(redisTemplate);
Rate limit = Rate.of(10, Refill.greedy(10, java.time.Duration.ofSeconds(1))); // 每秒10个请求
return Bucket4j.extension(RedisBucketBuilder.class)
.withManager(redisBucketManager)
.addLimit(limit)
.build("api_rate_limit_bucket"); // 使用一个键名来标识这个令牌桶
}
}
在这个配置中,我们通过RedisBucketManager管理器来管理令牌桶,这个管理器会将令牌桶的数据存储在Redis中。然后我们可以通过为每个桶设置名称使得每个节点,可以共享这个桶的令牌状态实现分布式限流的操作。
接下来就是实现一个过滤器来实现限流操作,这个过滤器的实现与单机版的过滤器实现方式类似,如下所示。只不过它操作的是Redis中存储的令牌通。
@Component
public class RedisRateLimitFilter extends OncePerRequestFilter {
@Autowired
private Bucket redisBucket;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain)
throws IOException, javax.servlet.ServletException {
ConsumptionProbe probe = redisBucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
// 令牌消耗成功,继续请求
filterChain.doFilter(request, response);
} else {
// 令牌桶已满或没有令牌,返回限流错误
response.setStatus(429);
response.getWriter().write("Too many requests. Please try again later.");
}
}
}
总结
通过集成Bucket4j,我们可以快速高效的实现API接口的限流操作,在实际场景中,我们可以选择使用内存中的令牌桶来实现单机限流,或者使用Redis来实现分布式限流。无论是单机限流还是分布式限流,Bucket4j都提供了高效并且灵活的配置方式来实现流量机制,确保了后端服务在高并发场景下的稳定性。