【问题标题】:Java Spring rate limiter block specific duration when ratio reached达到比率时的Java Spring速率限制器阻止特定持续时间
【发布时间】:2022-10-15 22:34:22
【问题描述】:

目前我有一个要求:为 API 应用速率限制器。如果此 API 每 5 秒调用超过 100 次,则该 API 将被阻塞 10 分钟。 我不知道是否有任何java lib可以满足这个要求。如果要求是“每 5 秒允许 100 个调用”或“每 10 分钟允许 100 个调用”,那么我可以使用 Bucket4j:

Bandwidth b = Bandwidth.classic(100, Refill.intervally(100, Duration.ofSeconds(5)));
//Bandwidth b = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(10)));
Bucket bk = Bucket.builder().addLimit(b).build();

//then
if(bk.tryConsume(1)) {
    //stuff
} else {
    throw
}

或 Resilence4j:

RateLimiterConfig config = RateLimiterConfig.custom()
            .limitRefreshPeriod(Duration.ofSeconds(5))
            .limitForPeriod(100)
            .timeoutDuration(Duration.ofSeconds(1))
            .build();
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);
RateLimiter rateLimiterWithCustomConfig = rateLimiterRegistry
            .rateLimiter("name2", config);
CheckedRunnable restrictedCall = RateLimiter
            .decorateCheckedRunnable(rateLimiterWithCustomConfig, this::doStuff);

//then
Try.run(restrictedCall).onFailure(throwable -> throw new RuntimeException());

但要求是“每 5 秒允许 100 个调用,如果更多,则阻止 10 分钟”。有什么lib可以工作吗?请建议我为这种情况提供解决方案。谢谢

【问题讨论】:

    标签: java spring rate-limiting resilience4j bucket4j


    【解决方案1】:

    目前有this feature for Bucket4j可以阻塞某个bucket一段时间。 (我可以在Resilence4j 中说同样的话,但我不太确定!)

    所以你应该手动维护黑名单。(这将需要更复杂的代码改进,具体取决于用例!)

    但是,我认为就代码成本而言,使用以下 2 个不同的限制会更好。 (您可以使 10 分钟的时间段保持一定的灵活性,并保持 5 秒的条件。)

    Bandwidth bandwidth1 = Bandwidth.classic(
            1200, Refill.intervally(
                    1200, Duration.ofMinutes(10)));
    Bandwidth bandwidth2 = Bandwidth.classic(
            100, Refill.intervally(
                    100, Duration.ofSeconds(5)));
    
    Bucket bucket = Bucket.builder()
            .addLimit(bandwidth1)
            .addLimit(bandwidth2)
            .build();
    

    如果我们想让手动黑名单成为一个简单的例子;

    您可以记录被阻止的时间。然后,您可以编写另一个类来检查何时达到解锁日期:

    import io.github.bucket4j.Bandwidth;
    import io.github.bucket4j.Bucket;
    import io.github.bucket4j.Refill;
    
    import java.time.Duration;
    import java.time.LocalDateTime;
    
    import static java.time.LocalDateTime.now;
    
    public class BucketWithPenaltyTest {
    
        public static void main(String[] args) throws InterruptedException {
    
            Bandwidth bandwidth = Bandwidth.classic(
                    10, Refill.intervally(
                            10, Duration.ofSeconds(5)));
    
            Bucket bucket = Bucket.builder()
                    .addLimit(bandwidth)
                    .build();
    
            BucketWithPenalty bucketWithPenalty =
                    new BucketWithPenalty(bucket, 
                            Duration.ofMinutes(1).getSeconds());
    
            while(true) {
                Thread.sleep(400);
                if (bucketWithPenalty.tryConsume(1)) {
                    System.out.println(String.format(
                            "[%s] %s", now(), 
                            "Ok."));
                } else {
                    System.err.println(String.format(
                            "[%s] %s", now(), 
                            "Rate limit error!"));
                }
            }
        }
    }
    
    import io.github.bucket4j.Bucket;
    import java.time.LocalDateTime;
    import static java.time.LocalDateTime.now;
    
    public class BucketWithPenalty {
    
        private static Bucket bucket;
        private static Long blockingTimeSeconds;
        private static LocalDateTime unblockingDate;
    
        public BucketWithPenalty(Bucket bucket, Long blockingTimeSeconds) {
            this.bucket = bucket;
            this.blockingTimeSeconds = blockingTimeSeconds;
            this.unblockingDate = now();
        }
    
        public boolean tryConsume(long numTokens) {
            if (unblockingDate.isBefore(now())
                    && bucket.tryConsume(numTokens)) {
                unblockingDate = now();
                return true;
            } else {
                if (!unblockingDate.isAfter(now())) {
                    unblockingDate = now().plusSeconds(blockingTimeSeconds);
                }
                return false;
            }
        }
    }
    

    【讨论】:

    • 我认为这不是我的期望。您的代码大约有两个带宽同时运行。我的期望是,如果每 5 秒调用超过 100 个,则阻塞 10 分钟,否则什么也不做
    • 我已经更新了评论以澄清更多。
    • Thanh你,如果你认为我的问题很有价值,请点赞,否则就忽略;)
    • 我认为它很有价值,因为有很多需要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 2013-03-31
    相关资源
    最近更新 更多