如何使用 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 -->
<dependency>
<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
public class RedisConfig {
@Bean
public Config config() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return config;
}
@Bean
public CacheManager cacheManager(Config config) {
CacheManager manager = Caching.getCachingProvider().getCacheManager();
cacheManager.createCache("cache", RedissonConfiguration.fromConfig(config));
return cacheManager;
}
@Bean
ProxyManager<String> proxyManager(CacheManager cacheManager) {
return new JCacheProxyManager<>(cacheManager.getCache("cache"));
}
}
这是做什么的?
- 创建一个我们可以用来创建连接的配置对象。
- 使用配置对象创建缓存管理器。 这将在内部创建到 Redis 实例的连接,并在其上创建一个名为 “cache” 的哈希。
- 创建将用于访问缓存的代理管理器。 无论我们的应用程序尝试使用 JCache API 缓存什么,它都会缓存在名为“cache”的哈希内的 Redis 实例上。
构建API
让我们创建一个简单的 REST API。
@RestController
public class RateLimitController {
@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 来查询它。
让我们一步一步地为它编写代码。
创建桶
在我们开始之前,让我们看一下桶是如何创建的。
Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
- Refill – 桶将在多长时间后重新装满。
- Bandwidth – 存储桶有多少带宽。 基本上,每个补充周期的请求。
- Bucket – 使用这两个参数配置的对象。 此外,它还维护一个令牌计数器来跟踪存储桶中有多少令牌可用。
使用它作为构建块,让我们更改一些内容以使其适合我们的用例。
使用 ProxyManager 创建和缓存存储桶
我们创建代理管理器的目的是在 Redis 上缓存存储桶。 Bucket一旦创建,就需要缓存在Redis上,不需要再次创建。
为了实现这一点,我们将 Bucket4j.builder()
替换为 proxyManager.builder()
。 ProxyManager 将负责缓存存储桶而不是再次创建它们。
ProxyManager 的构建器有两个参数——一个用于缓存存储桶的键和一个用于创建存储桶的配置对象。
让我们看看如何实现它:
@Service
public class RateLimiter {
//自动装配依赖项
public Bucket resolveBucket(String key) {
Supplier<BucketConfiguration> configSupplier = getConfigSupplierForUser(key);
// 并不总是创建一个新的存储桶,而是返回现有的存储桶(如果存在)。
return buckets.builder().build(key, configSupplier);
}
private Supplier<BucketConfiguration> getConfigSupplierForUser(String key) {
User user = userRepository.findById(userId);
Refill refill = Refill.intervally(user.getLimit(), Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(user.getLimit(), refill);
return () -> (BucketConfiguration.builder()
.addLimit(limit)
.build());
}
}
我们创建了一个方法,它为提供的密钥返回一个存储桶。 在下一步中,我们将看到如何使用它。
如何使用Token并设置速率限制
当请求进来时,我们将尝试从相关存储桶中消费一个令牌。
我们将使用存储桶的 tryConsume() 方法来执行此操作。
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
// 获取用户的存储桶
Bucket bucket = 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
public void testGetInfo() {
// 为用户 1 调用该方法 10 次
for (int i = 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 客户端以及如何使用它来缓存存储桶。
最后,我们实现了一个简单的频率限制器,可用于对特定用户的请求进行频率限制。
希望您喜欢本文章。 谢谢阅读!
相关文章
如何在 Django 应用程序中使用 Redis 进行缓存
发布时间:2023/02/06 浏览次数:111 分类:Python
-
减轻服务器压力的方法之一是缓存数据。 这是通过在处理数据后缓存数据,然后在下次请求时从缓存中提供数据来完成的。 本篇文章将详细讨论 Redis,解释如何在 Python 应用程序中安装
如何使用 Node.js 在 Redis Cloud 上存储和检索 RedisJSON
发布时间:2022/09/07 浏览次数:90 分类:学无止境
-
在过去的几个月里,我一直在使用 Redis 数据库来管理我的一些应用程序,随着应用程序的扩展,我发现我正在使用更多的 Redis 功能。其中一项功能是 **Redis Stack**。在深入了解 Redis Sta
通过 Homebrew 在 Mac OS X 上安装和配置 Redis
发布时间:2022/09/07 浏览次数:252 分类:操作系统
-
通过使用 Homebrew,可以大大降低在 Mac OS X 上设置和配置开发环境的成本。让我们安装 Redis。
mac系统使用 clion远程调试redis4源码
发布时间:2021/05/08 浏览次数:151 分类:Redis
-
本篇介绍在mac系统下使用clion对Redis进行远程源代码调试。主要适用sftp同步代码,gdbserver开启远程调试服务。linux作为redis运行的远程服务器。
如何在mac系统下使用clion调试redis源码
发布时间:2021/04/29 浏览次数:368 分类:Redis
-
本篇主要介绍在mac系统下如何使用clion调试redis的源码。clion主要使用的是`cmake` + `make` 进行编译。所以对于redis4来说,主要的就是先编写 CMakeLists.txt 文件
Redis 在 centos下安装方法及常见问题解决
发布时间:2017/08/18 浏览次数:933 分类:Redis
-
本篇我们来介绍一下如何在Centos下安装redis。其实,安装步骤很简单,只是中间可能会出现一些问题,这是值得我们所关注的。
自定义的PHPRedis操作类使用介绍
发布时间:2016/05/12 浏览次数:608 分类:PHP
-
PHP封装了一个Redis的操作类,其中涉及的函数command、exec、result、get_errinfo等。本篇分别介绍了如何使用这些函数。
Redis密码验证命令AUTH
发布时间:2016/05/11 浏览次数:5140 分类:Redis
-
Redis在安全方面并没有做太大的优化,而是在性能和易用性方面下了很大的功夫。Redis一个很简单的安全方式就是密码验证,这需要用到AUTH命令。