使用bucket4j,redis,postgres in Springboot中的redis速率限制
为什么bucket4j
您可以使用Bucket4J进行限制的原因有很多。这是一些最重要的:
- 这是一个成熟且经过充分测试的库。 Bucket4J已经存在了很多年,并且已被许多项目使用。这意味着它是一个可靠且稳定的库,您可以充满信心。
- 这很容易使用。 Bucket4J具有简单明了的API,可以轻松实现代码中的速率限制。您无需成为限制费率的专家即可开始使用Bucket4J。
- 这是灵活的。 Bucket4J允许您以多种方式配置速率限制。您可以指定每秒,分钟,小时或一天的请求数。您还可以指定突发限制,这允许在短时间内在常规限制以上提出一定数量的请求。
- 这是有效的。 Bucket4j是一个非常有效的库。它使用令牌存储算法来跟踪请求,这是一种非常有效的限制限制方法。这意味着Bucket4J不会对您的应用程序的性能产生重大影响。
总的来说,Bucket4J是Java限制费率的绝佳选择。它是一个成熟,经过良好测试,易于使用,灵活且高效的库。
以下是使用bucket4j的其他好处:
- 它可以保护您的API免受DDOS攻击。通过限制可以向您的API提出的请求的数量,您可以使攻击者难以用请求淹没您的服务器。
- 它可以确保公平的资源分配。通过限制每个客户可以提出的请求数量,您可以确保所有客户端都可以公平地访问API资源。
- 它可以提高API的性能。通过限制可以提出的请求的数量,您可以减少服务器上的负载,这可以提高API的性能。
如果您正在为Java应用程序寻找限制速率库,我强烈建议Bucket4j。这是保护API免受DDOS攻击,确保公平资源分配并提高API的性能的绝佳选择。
现在让我们立即在Spring Boot上进行实际实现。
以下是实施的依赖项。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.giffing.bucket4j.spring.boot.starter/bucket4j-spring-boot-starter -->
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
实施是基于Maven的,并将Java 17用于实现。
配置软件包下的配置
- 重新案件
package Rateimiting.config;
import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver;
import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheCacheResolver;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.grid.jcache.JCacheProxyManager;
import org.redisson.config.Config;
import org.redisson.jcache.configuration.RedissonConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.cache.CacheManager;
import javax.cache.Caching;
@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();
manager.createCache("cache", RedissonConfiguration.fromConfig(config));
return manager;
}
@Bean
ProxyManager<String> proxyManager(CacheManager cacheManager){
return new JCacheProxyManager<>(cacheManager.getCache("cache"));
}
@Bean
@Primary
public SyncCacheResolver bucket4jCacheResolver(CacheManager cacheManager){
return new JCacheCacheResolver(cacheManager);
}
}
config bean
此Bean为Redis创建了配置对象。此对象指定REDIS服务器的地址
CacheManager Bean
此Bean创建了一个使用REDIS作为备用商店的缓存管理器。此缓存管理器将用于存储速率限制。
代理人豆
此Bean创建了一个代理管理器,可用于访问缓存中的速率限制。该代理管理器使使用代码中的速率限制变得容易。
Synccacheresolver Bean
此Bean创建了一个使用缓存管理器来解决速率限制的缓存解析器。 Bucket4j使用此缓存解析器来确定给定请求的当前速率限制。
@primary注释:
此注释表明这是Bucket4J应该使用的默认缓存解析器。
- RatelimitConfig
package Rateimiting.config;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.BucketConfiguration;
import io.github.bucket4j.Refill;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.util.function.Supplier;
@Configuration
public class RateLimitConfig {
@Autowired
public ProxyManager buckets;
public Bucket resolveBucket(String key,int tps){
Supplier<BucketConfiguration> configurationSupplier = getConfigSupplier(key,tps);
return buckets.builder().build(key,configurationSupplier);
}
private Supplier<BucketConfiguration> getConfigSupplier(String key, int tps){
Refill refill=Refill.intervally(tps, Duration.ofSeconds(1));
Bandwidth limit=Bandwidth.classic(tps,refill);
return () -> (BucketConfiguration.builder()
.addLimit(limit)
.build());
}
}
ResolveBucket()方法:
此方法采用两个参数:存储桶的密钥和存储桶应允许的每秒请求数(TPS)。该方法首先调用getConfigsupplier()方法来获取桶装对象的供应商。然后,该方法使用BucketConfiguration对象的供应商创建一个桶对象。存储桶对象用于评分限制请求。
getConfigsupplier()方法:
此方法创建了一个桶装对象,该对象指定了存储桶的速率限制。速率限制由带宽对象指定。带宽对象指定了存储桶每秒允许的请求数和补充速率。补充速率指定了用令牌补充桶的频率。
在我的型号软件包中
模型 - >请求
package Rateimiting.model.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Request {
private String username;
private String Message;
}
这代表我期望从邮政请求的样本请求主体。
模型apiresponse
package Rateimiting.model;
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse {
private String message;
private String responseCode;
private String status;
}
预期的API响应
TPSDB模型
package Rateimiting.model;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
@Data
@Table(name="tbl_tps")
@Entity
public class TpsDb implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String path;
private int tps;
}
这表示TPS详细信息存储在DB中的表。
存储库
package Rateimiting.repository;
import Rateimiting.model.TpsDb;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TpsDbRepository extends JpaRepository<TpsDb, Long> {
TpsDb findByUsernameAndPath(String username, String path);
}
添加了查询TPS详细信息我的用户名和发布请求的路径。
服务
package Rateimiting.service;
import Rateimiting.config.RateLimitConfig;
import Rateimiting.model.ApiResponse;
import Rateimiting.model.TpsDb;
import Rateimiting.model.request.Request;
import Rateimiting.repository.TpsDbRepository;
import io.github.bucket4j.Bucket;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@Service
public class RateLimitService {
int tps;
@Autowired
private RedissonClient redissonClient;
private final TpsDbRepository tpsD;
private final RateLimitConfig rateLimitConfig;
@Autowired
public RateLimitService(TpsDbRepository tpsD, RateLimitConfig rateLimitConfig) {
this.tpsD = tpsD;
this.rateLimitConfig = rateLimitConfig;
}
public ResponseEntity<?> addInfo(Request request, String path) {
String username = request.getUsername();
// Check if the TpsDb is cached in Redis
RMapCache<String, TpsDb> cache = redissonClient.getMapCache("tpsDbCache");
TpsDb tpsDb = cache.get(username + "-" + path);
if (tpsDb == null) {
tpsDb = tpsD.findByUsernameAndPath(path, username);
if (tpsDb == null) {
tpsDb = new TpsDb();
tpsDb.setUsername(username);
tpsDb.setPath(path);
tpsDb.setTps(10); // Default TPS value if not found in the database
}
cache.put(username + "-" + path, tpsDb);
}
int tps = tpsDb.getTps();
Bucket bucket = rateLimitConfig.resolveBucket(username, tps);
if (bucket.tryConsume(1)) {
return ResponseEntity.status(200).body(new ApiResponse("Request Success for user " + username, "4000", "success"));
} else {
return ResponseEntity.status(429).body(new ApiResponse("Request failed for user " + username, "4003", "failed"));
}
}
}
addinfo()方法:
此方法将请求对象和路径作为输入,并返回响应,指示该请求是否成功。该方法首先检查tpsdb对象是否在redis中缓存。如果未缓存TPSDB对象,则该方法将从数据库中检索。如果数据库中未找到tpsdb对象,则该方法将创建一个具有默认TPS值的新TPSDB对象。然后,该方法使用用户ID和TPSDB对象的TPS值创建一个存储桶对象。存储桶对象用于限制请求。如果请求成功,该方法将返回200个状态代码和成功消息。否则,该方法返回429状态代码和错误消息。
最后,
控制器。
创建测试帖子请求
package Rateimiting.controller;
import Rateimiting.model.request.Request;
import Rateimiting.service.RateLimitService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.HandlerMapping;
@RestController
@RequestMapping("/v1/")
public class RateLimitController {
@Autowired
private HttpServletRequest requests;
private final RateLimitService rateLimitService;
@Autowired
public RateLimitController(RateLimitService rateLimitService) {
this.rateLimitService = rateLimitService;
}
@PostMapping("/rate")
public ResponseEntity<?> addInfo(@RequestBody Request request){
String uri = requests.getRequestURI();
String path = (String) requests.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return rateLimitService.addInfo(request, path);
}
}
@postmapping(“/rate”)注释:
此注释将AddInfo()方法标记为可用于在速率限制系统中添加新请求的后端点。
@requestbody请求请求参数:
此参数指定Addinfo()方法将请求对象作为输入。
字符串uri = requests.getRequesturi()语句:
此语句获取请求的URI。 URI用于获取所请求的资源的路径。
字符串路径=(string)requests.getAttribute(handlermapping.path_within_handler_handler_mapping_attribute)语句:
此语句获取从处理对象请求的资源的路径。
返回ratelimitservice.addinfo(请求,路径);语句:此语句在Ratelimitservice类上调用AddInfo()方法,以将请求添加到速率限制系统中。
application.properties
spring.sql.init.platform=postgresql
spring.datasource.url=jdbc:postgresql://localhost:5432/dbname
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.jpa.hibernate.ddl-auto=update
您可以在我的git repo here
上访问代码