bucket4j,redis,postgres in Springboot限制速率
#springboot #redis #bucket4 #ratelimiting

使用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用于实现。

配置软件包下的配置

  1. 重新案件
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应该使用的默认缓存解析器。

  1. 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

上访问代码