目录
前言
限流是一种常见的保护机制,保护系统的稳定性和可用性。实际的运用场景有:
- API限流:保护后端系统不被过多的API请求压垮。
- 资源访问限制:限制对数据库、文件系统等资源的访问频率。
- 防止爬虫滥用:限制来自特定IP或用户的请求频率。
一、RateLimiter的使用
1. RateLimiter简介
RateLimiter是Guava库提供的一个简单有效的限流工具,基于令牌桶算法实现。令牌桶可以看作是一个容器,按照一定的速率向里面添加令牌。当有请求到达时,会尝试从桶中取出一个令牌,如果成功则允许请求通过,否则请求将被限流。
2. 使用步骤
在pom.xml文件中引入库:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
RateLimiter.create(double permitsPerSecond)
方法创建一个RateLimiter实例,指定每秒添加的令牌数。有多个重写方法。
acquire(int permits)
方法指定一次消耗多少个令牌。桶中令牌数量足够就使用,令牌数量不足时则等待,从而阻止了代码的运行,达到限流的效果。
tryAcquire()
方法允许非阻塞尝试获取令牌,返回boolean值用于判断。
示例代码:
import com.google.common.util.concurrent.RateLimiter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class RateLimiterDemo {
public static void main(String[] args) {
// 创建一个RateLimiter,每秒生成1个令牌
RateLimiter rateLimiter = RateLimiter.create(1);
for (int i = 1; i <= 5; i++) {
// 请求RateLimiter, 超过permits会被阻塞
rateLimiter.acquire(1); //一次消耗1个令牌
//rateLimiter.acquire(2); //一次消耗2个令牌
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
}
}
}
示例代码中有两个设置:rateLimiter.acquire(1); //一次消耗1个令牌 和 rateLimiter.acquire(2); //一次消耗2个令牌。
一次消耗1个令牌的运行结果:每 1 秒执行一次限流代码
一次消耗2个令牌的运行结果:每 2 秒执行一次限流代码
二、Bucket4j的使用
1. Bucket4j简介
Bucket4j是为 Java 设计的高性能、灵活且易于使用的限流库,核心是通过令牌桶算法对流入系统的请求进行限速,可以预先设置好“桶”的容量和“令牌”生成速率。
2. 使用步骤
在pom.xml文件中引入库,版本6.0.2,后续版本可能出现与代码不匹配:
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>6.0.2</version>
</dependency>
Bandwidth
:定义带宽,即单位时间产生的令牌个数。
Bucket
:构建桶,存放令牌。
tryConsume(long var1):尝试消费多少个令牌,返回布尔值用于判断。
asScheduler():阻塞等待,直到获取到令牌才进行后续语句的执行。
示例代码:
import io.github.bucket4j.*;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
public class Bucket4JDemo {
/**
* 阻塞,asScheduler()会进行阻塞,直到获取令牌才进行后续语句的执行。
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// 定义带宽,simple(令牌个数,单位时间):单位时间产生的令牌个数
Bandwidth limit = Bandwidth.simple(1, Duration.ofSeconds(1));
// 构建桶,令牌加入桶中
Bucket bucket = Bucket4j.builder().addLimit(limit).build();
for (int i = 1; i <= 5; i++) {
bucket.asScheduler().consume(2); //consume()指定一次消费令牌的个数
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
}
}
}
运行结果:每 2 秒执行一次限流代码。因为令牌每次消费2个、而令牌产生的速率是每秒产生一个令牌。
Refiller
:填充速度
classic(long capacity, Refill refill):用Refiller创建Bandwidth,第一个参数指定桶的容量
withInitialTokens(long initialTokens):桶初始化令牌数。
示例代码:
public static void main(String[] args) throws InterruptedException {
//Refiller:令牌填充速率(单位时间产生的令牌个数)
Refill filler = Refill.greedy(5, Duration.ofSeconds(1));
//classic:用Refiller创建Bandwidth,第一个参数指定桶的容量。withInitialTokens()桶初始化令牌数
Bandwidth limit = Bandwidth.classic(20, filler).withInitialTokens(2);
// 令牌加入桶中
Bucket bucket = Bucket4j.builder().addLimit(limit).build();
for (int i = 1; i <= 5; i++) {
bucket.asScheduler().consume(1); //consume()指定一次消费令牌的个数
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
}
}
配置热替换:
// 配置热替换 BucketConfiguration config = Bucket4j.configurationBuilder() .addLimit(Bandwidth.simple(5, Duration.ofSeconds(1))) .build(); bucket.replaceConfiguration(config,TokensInheritanceStrategy.RESET);
示例代码:
public static void main(String[] args) throws InterruptedException {
// 定义带宽,simple(令牌个数,单位时间):单位时间产生的令牌个数
Bandwidth limit = Bandwidth.simple(1, Duration.ofSeconds(1));
// 构建桶,令牌加入桶中
Bucket bucket = Bucket4j.builder().addLimit(limit).build();
// 配置热替换
BucketConfiguration config = Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(5, Duration.ofSeconds(1)))
.build();
bucket.replaceConfiguration(config,TokensInheritanceStrategy.RESET);
for (int i = 1; i <= 5; i++) {
bucket.asScheduler().consume(2); //consume()指定一次消费令牌的个数
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
}
}
总结
本篇只是初步对RateLimiter与Bucket4j的使用进行了说明,作为个人的工作参考使用。谢谢关注!