Guava's rate limiter is pretty good but I didn't want to include a (fat) dependency on guava just for one class so I wrote a variant based on Java's DelayQueue:
class DelayedEntry implements Delayed {
long expireAt;
TimeUnit unit;
DelayedEntry(long delay, TimeUnit tu) {
unit = tu;
setDelay(delay);
}
void setDelay(long delay) {
this.expireAt = System.nanoTime() + unit.toNanos(delay);
}
int compareTo(Delayed other) {
throw new IllegalStateException("Expected single element queue");
}
long getDelay(TimeUnit u) {
return u.convert(expireAt - System.nanoTime(), NANOSECONDS);
}
}
class RateLimiter {
DelayQueue<DelayedEntry> queue;
DelayedEntry token;
TimeUnit rateUnit;
AtomicInteger rate;
RateLimiter(int rateLimit) {
queue = new DelayQueue<>();
rateUnit = NANOSECONDS;
rate = new AtomicInteger(rateLimit);
token = new DelayedEntry(0, NANOSECONDS);
}
boolean acquire(int permits) throws InterruptedException {
long targetDelay = rateUnit.toNanos(permits) / max(1, rate.get());
DelayedEntry nextToken = token;
while (!queue.isEmpty()) {
nextToken = queue.take();
}
assert nextToken != null;
nextToken.setDelay(targetDelay);
return queue.offer(token);
}
}This implementation isn't exactly a mathematically precise rate limiter that can shape traffic bursts in uniform distributions for volatile rate limits. However, for use-cases involving predictable timings, this works pretty well with minimal and bounded resource usage.One thing that shouldn't go un-noticed is that we can easily use such utility implementations to instrument collection interfaces such as Iterable and Iterator to decorate existing code base, here's an example:
public <T> Iteratordecorate(final Iterator<T> delegate) { return new Iterator () { public boolean hasNext() { return delegate.hasNext(); } public T next() { acquire(); return delegate.next(); } public void remove() { delegate.remove(); } }; }
I would love to hear reader's opinions about this approach.
No comments:
Post a Comment