如何使用 Bucket4J 和 Redis 创建频率限制器
在本教程中,我们将学习如何在扩展服务中实现频率限制。
我们将使用 Bucket4J 库来实现它,我们将使用 Redis 作为分布式缓存。
为什么使用频率限制?
让我们从一些基础知识开始,以确保我们了解频率限制的必要性并介绍我们将在本教程中使用的工具。
无限频率的问题
如果公共 API 允许其用户每小时发出无限数量的请求,它可能会导致:
资源枯竭
服务质量下降
拒绝服务攻击
这可能会导致服务不可用或速度缓慢的情况。 它还可能导致服务产生更多意想不到的成本。
频率限制的好处
首先,限速可以防止拒绝服务攻击。 当与重复数据删除机制或 API 密钥结合使用时,速率限制还可以帮助防止分布式拒绝服务攻击。
其次,它有助于估计流量。 这对于公共 API 非常重要。 这也可以结合自动化脚本来监控和扩展服务。
第三,我们可以使用它来实施基于层的定价。 这种定价模式意味着用户可以为更高的请求率付费。
Token Bucket 算法
Token Bucket 是一种可以用来实现速率限制的算法。 简而言之,它的工作原理如下:
创建具有一定容量(令牌数)的存储桶。
当请求进来时,会检查存储桶。 如果有足够的容量,则允许请求继续进行。 否则,请求被拒绝。
当请求被允许时,容量会减少。
一定时间后,容量得到补充。
如何在分布式系统中实现 Token Bucket
要在分布式系统中实现 Token Bucket 算法,我们需要使用分布式缓存。
缓存是存储桶信息的键值存储。 我们将使用 Redis 缓存来实现这一点。
在内部,Bucket4j 允许我们插入 Java JCache API 的任何实现。 Redis 的 Redisson 客户端就是我们将要使用的实现。
项目实现
我们将使用 Spring Boot 框架来构建我们的服务。
我们的服务将包含以下组件:
一个简单的 REST API。
连接到服务的 Redis 缓存 - 使用 Redisson 客户端。
Bucket4J 库围绕着 REST API。
我们将 Bucket4J 连接到 JCache 接口,该接口将在后台使用 Redisson 客户端作为实现。
首先,我们将学习对所有请求的 API 进行速率限制。 然后我们将学习为每个用户或每个定价层实施更复杂的频率限制机制。
让我们从项目设置开始。
安装依赖项
让我们将以下依赖项添加到我们的 pom.xml
(或 build.gradle)文件中。
<dependencies>
<!-- To build the Rest API -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redisson Starter = Spring Data Redis starter(excluding other clients) and Redisson client -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
<!-- Bucket4J starter = Bucket4J + JCache -->
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>0.5.2</version>
</dependency>
</dependencies>
缓存配置
首先,我们需要启动我们的 Redis 服务器。 假设我们在本地机器的 6379 端口上运行了一个 Redis 服务器。
我们需要执行两个步骤:
从我们的应用程序创建到该服务器的连接。
设置 JCache 以使用 Redisson 客户端作为实现。
Redisson 的文档提供了在常规 Java 应用程序中实现此功能的简明步骤。 我们将在 Spring Boot 中实现相同的步骤。
我们先来看代码。 我们需要创建一个配置类来创建所需的 bean。
@Configuration
publicclassRedisConfig {
@Bean
public Config config() {
Configconfig=newConfig();
config.useSingleServer().setAddress("redis://localhost:6379");
return config;
}
@Bean
public CacheManager cacheManager(Config config) {
CacheManagermanager= Caching.getCachingProvider().getCacheManager();
cacheManager.createCache("cache", RedissonConfiguration.fromConfig(config));
return cacheManager;
}
@Bean
ProxyManager<String> proxyManager(CacheManager cacheManager) {
returnnewJCacheProxyManager<>(cacheManager.getCache("cache"));
}
}
这是做什么的?
创建一个我们可以用来创建连接的配置对象。
使用配置对象创建缓存管理器。 这将在内部创建到 Redis 实例的连接,并在其上创建一个名为 “cache” 的哈希。
创建将用于访问缓存的代理管理器。 无论我们的应用程序尝试使用 JCache API 缓存什么,它都会缓存在名为“cache”的哈希内的 Redis 实例上。
构建API
让我们创建一个简单的 REST API。
@RestController
publicclassRateLimitController {
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
return"Hello " + id;
}
}
如果我使用 URL http://localhost:8080/user/1 访问 API,我将得到响应 Hello 1。
Bucket4J 配置
要实现频率限制,我们需要配置 Bucket4J。 值得庆幸的是,由于启动库,我们不需要编写任何样板代码。
它还会自动检测我们在上一步中创建的 ProxyManager bean,并使用它来缓存存储桶。
我们需要做的是围绕我们创建的 API 配置这个库。同样有多种方法可以做到这一点。
我们可以选择在 starter 库中定义的基于属性的配置。对于所有用户或所有访客用户的频率限制等简单情况,这是最方便的方法。
但是,如果我们想为每个用户实现更复杂的东西,比如频率限制,最好为它编写自定义代码。
我们将实施每个用户的频率限制。 假设我们在数据库中存储了每个用户的频率限制,我们可以使用用户 ID 来查询它。
让我们一步一步地为它编写代码。
创建桶
在我们开始之前,让我们看一下桶是如何创建的。
Refillrefill= Refill.intervally(10, Duration.ofMinutes(1));
Bandwidthlimit= Bandwidth.classic(10, refill);
Bucketbucket= Bucket4j.builder()
.addLimit(limit)
.build();
Refill – 桶将在多长时间后重新装满。
Bandwidth – 存储桶有多少带宽。 基本上,每个补充周期的请求。
Bucket – 使用这两个参数配置的对象。 此外,它还维护一个令牌计数器来跟踪存储桶中有多少令牌可用。
使用它作为构建块,让我们更改一些内容以使其适合我们的用例。
使用 ProxyManager 创建和缓存存储桶
我们创建代理管理器的目的是在 Redis 上缓存存储桶。 Bucket一旦创建,就需要缓存在Redis上,不需要再次创建。
为了实现这一点,我们将 Bucket4j.builder()
替换为 proxyManager.builder()
。 ProxyManager 将负责缓存存储桶而不是再次创建它们。
ProxyManager 的构建器有两个参数——一个用于缓存存储桶的键和一个用于创建存储桶的配置对象。
让我们看看如何实现它:
@Service
publicclassRateLimiter {
//自动装配依赖项
public Bucket resolveBucket(String key) {
Supplier<BucketConfiguration> configSupplier = getConfigSupplierForUser(key);
// 并不总是创建一个新的存储桶,而是返回现有的存储桶(如果存在)。
return buckets.builder().build(key, configSupplier);
}
private Supplier<BucketConfiguration> getConfigSupplierForUser(String key) {
Useruser= userRepository.findById(userId);
Refillrefill= Refill.intervally(user.getLimit(), Duration.ofMinutes(1));
Bandwidthlimit= Bandwidth.classic(user.getLimit(), refill);
return () -> (BucketConfiguration.builder()
.addLimit(limit)
.build());
}
}
我们创建了一个方法,它为提供的密钥返回一个存储桶。 在下一步中,我们将看到如何使用它。
如何使用Token并设置速率限制
当请求进来时,我们将尝试从相关存储桶中消费一个令牌。
我们将使用存储桶的 tryConsume() 方法来执行此操作。
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
// 获取用户的存储桶
Bucketbucket= rateLimiter.resolveBucket(id);
// 尝试使用存储桶中的令牌
if (bucket.tryConsume(1)) {
return"Hello " + id;
} else {
return"Rate limit exceeded";
}
}
如果令牌被成功消费,tryConsume() 方法返回 true,如果令牌未被消费,则返回 false。
如何测试我们的服务
我们可以使用任何自动化测试技术对此进行测试。 例如,我们可以使用 JUnit
。 让我们编写一个测试用例,多次调用 getInfo() 方法并验证响应是否正确。
假设我们有一个 id 为 1 的用户,并且限制为每分钟 10 个请求。 假设我们还有一个 id 为 2 的用户,限制为每分钟 20 个请求。
我们将为两个用户发送 11 个请求,并验证 ID 为 1 的用户的请求失败,但 ID 为 2 的用户的请求成功。
@Test
publicvoidtestGetInfo() {
// 为用户 1 调用该方法 10 次
for (inti=0; i < 10; i++) {
rateLimiter.getInfo(1));
rateLimiter.getInfo(2));
}
// 验证用户 1 的响应是否受频率限制
assertEquals("Rate limit exceeded", rateLimiter.getInfo(1));
// 验证用户 2 的响应是否成功
assertEquals("Hello 2", rateLimiter.getInfo(2));
}
当我们运行测试时,我们会看到测试通过了。
总结
在本教程中,我们介绍了如何在 Spring Boot 应用程序中使用 Bucket4j 和 Redis 创建频率限制器。我们还研究了如何使用 JCache 设置 Redisson 客户端以及如何使用它来缓存存储桶。
最后,我们实现了一个简单的频率限制器,可用于对特定用户的请求进行频率限制。
希望您喜欢本文章。 谢谢阅读!
本文转载自:迹忆客(https://www.jiyik.com)
以上是 如何使用 Bucket4J 和 Redis 创建频率限制器 的全部内容, 来源链接: utcz.com/z/290326.html