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个令牌,和开启信用额度的用户表现一致。要体现差异,需要模拟突发请求场景:让请求在短时间内集中爆发,此时开启信用额度的用户可以利用累积的令牌(信用)处理更多请求,而关闭的用户则会被严格限流。
具体修改步骤
- 同时创建两个用户实例:一个开启信用额度,一个关闭
- 在每个时间窗口内发送远超限额的请求,触发限流
- 分别统计两个用户的请求通过数,对比差异
修改后的驱动代码
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
相关产品推荐
相关产品推荐

