You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java令牌桶限流器:如何区分有无credit limit用户的请求差异?

问题描述

我用Java实现了一个令牌桶限流器,想要模拟两个用户:一个开启信用额度,一个关闭信用额度,让开启信用额度的用户能通过更多请求,以此体现限流器的差异。但目前无论是否开启信用额度,两类用户通过的请求数始终相同。

当前运行时长10秒的循环,每个时间窗口(每秒)允许50个请求,总计能通过500个请求。请问如何修改驱动代码,让关闭信用额度的用户通过的请求数更少?

现有代码实现

Application类

public class Application {
    public static void main(String[] args) throws InterruptedException {
        UserBucketCreator userBucketCreator1 = new UserBucketCreator(1, false);

        int totalTime = 10 * 1000;
        long startTime = System.currentTimeMillis();
        int numberOfConsumed = 0;
        int totalRequests = 0;
        while ((System.currentTimeMillis() - startTime) < totalTime) {
            totalRequests++;
            boolean consumeSuccess = userBucketCreator1.accessApplication();
            if (consumeSuccess) {
                numberOfConsumed++;
            }
        }
        System.out.println("total no of requests made = " + totalRequests);
        System.out.println("no of consumed request = " + numberOfConsumed);
        System.out.println("time taken = " + totalTime);
        System.out.println("no of request per window expected = " + TokenBucketConstants.numberOfRequest);
    }
}

TokenBucket类

public class TokenBucket {
    private int numberOfTokenAvailable;
    private int numberOfRequests;
    private int windowSizeForRateLimitInMilliSeconds;
    private long lastRefillTime;
    private long nextRefillTime;
    private int maxBucketSize;
    private boolean applyCreditRequests;

    public TokenBucket(int maxBucketSize, int numberOfRequests, int windowSizeForRateLimitInMilliSeconds, boolean applyCreditRequests) {
        this.maxBucketSize = maxBucketSize;
        this.numberOfRequests = numberOfRequests;
        this.windowSizeForRateLimitInMilliSeconds = windowSizeForRateLimitInMilliSeconds;
        this.applyCreditRequests = applyCreditRequests;
        this.refill();
    }

    public boolean tryConsume() {
        refill();
        if (this.numberOfTokenAvailable > 0) {
            this.numberOfTokenAvailable--;
            return true;
        }
        return false;
    }

    private void refill() {
        if (System.currentTimeMillis() < this.nextRefillTime) {
            return;
        }
        this.lastRefillTime = System.currentTimeMillis();
        this.nextRefillTime = this.lastRefillTime + this.windowSizeForRateLimitInMilliSeconds;
        if (applyCreditRequests) {
            int creditRequest = this.numberOfTokenAvailable + this.numberOfRequests;
            this.numberOfTokenAvailable = Math.min(this.maxBucketSize, creditRequest);
        } else {
            this.numberOfTokenAvailable = this.numberOfRequests;
        }
    }
}

TokenBucketConstants类

public class TokenBucketConstants {
    public static int numberOfRequest = 50;

    public static int windowSizeForRateLimitInMilliSeconds = 1 * 1000;

    public static int maxBucketSize = 100;
}

UserBucketCreator类

@Getter
@Setter
public class UserBucketCreator {
    private int userId;
    int maxCredits;
    Map<Integer, TokenBucket> bucket;

    public UserBucketCreator(int id, boolean applyCreditRequests) {
        this.bucket = new HashMap<>();
        this.userId = id;
        bucket.put(id, new TokenBucket(TokenBucketConstants.maxBucketSize,
                TokenBucketConstants.numberOfRequest,
                TokenBucketConstants.windowSizeForRateLimitInMilliSeconds, applyCreditRequests));
    }

    boolean accessApplication() {
        return bucket.get(userId).tryConsume();
    }
}
问题分析与修改方案

核心问题是当前请求发送速率过于均匀,无法触发信用额度的差异——关闭信用额度的用户每个窗口刚好能用完50个令牌,和开启信用额度的用户表现一致。要体现差异,需要模拟突发请求场景:让请求在短时间内集中爆发,此时开启信用额度的用户可以利用累积的令牌(信用)处理更多请求,而关闭的用户则会被严格限流。

具体修改步骤

  1. 同时创建两个用户实例:一个开启信用额度,一个关闭
  2. 在每个时间窗口内发送远超限额的请求,触发限流
  3. 分别统计两个用户的请求通过数,对比差异

修改后的驱动代码

public class Application {
    public static void main(String[] args) throws InterruptedException {
        // 创建两个对比用户:开启信用/关闭信用
        UserBucketCreator userWithCredit = new UserBucketCreator(1, true);
        UserBucketCreator userWithoutCredit = new UserBucketCreator(2, false);

        int totalWindows = 10;
        int windowMs = TokenBucketConstants.windowSizeForRateLimitInMilliSeconds;
        int burstRequestsPerWindow = 100; // 每个窗口发送100个请求,远超50的限额

        int consumedWithCredit = 0;
        int consumedWithoutCredit = 0;

        for (int i = 0; i < totalWindows; i++) {
            long windowStart = System.currentTimeMillis();
            // 在当前窗口内快速发送突发请求
            for (int j = 0; j < burstRequestsPerWindow; j++) {
                if (userWithCredit.accessApplication()) {
                    consumedWithCredit++;
                }
                if (userWithoutCredit.accessApplication()) {
                    consumedWithoutCredit++;
                }
            }
            // 等待当前窗口结束,进入下一个窗口
            long elapsed = System.currentTimeMillis() - windowStart;
            if (elapsed < windowMs) {
                Thread.sleep(windowMs - elapsed);
            }
        }

        System.out.println("=== 开启信用额度的用户 ===");
        System.out.println("通过的请求数: " + consumedWithCredit);
        System.out.println("=== 关闭信用额度的用户 ===");
        System.out.println("通过的请求数: " + consumedWithoutCredit);
    }
}

差异体现逻辑

  • 开启信用额度的用户:每个窗口会将剩余未用完的令牌与新生成的50个累加(最多到maxBucketSize=100)。第一个窗口可以通过100个请求,后续窗口如果有剩余令牌,也能处理更多请求,总通过数会超过500。
  • 关闭信用额度的用户:每个窗口会直接重置为50个令牌,所以每个窗口最多通过50个请求,10个窗口总计500个。

内容的提问来源于stack exchange,提问作者Arpan Banerjee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:35:14