How to Build a Simple Token Bucket RateLimiter in Java

This article provides an overview of the token bucket algorithm, demonstrates a straightforward Java implementation of a RateLimiter with code examples, compares it to Guava's RateLimiter internals, and includes diagrams and test results to illustrate its behavior.

Programmer DD
Programmer DD
Programmer DD
How to Build a Simple Token Bucket RateLimiter in Java

Preface

This article is not a detailed analysis of RateLimiter, but rather an overview.

Token Bucket Algorithm

The token bucket logic is the basis of most RateLimiter implementations.

Implementation by Diagram

Following the common diagram, the implementation simply adds tokens at a fixed rate and provides a method to acquire a token.

import java.util.concurrent.*;

public class MyRateLimiter {
    // Token bucket
    BlockingQueue<Integer> TOKEN_BUCKET = new LinkedBlockingDeque<>(5);

    public static void main(String[] args) {
        MyRateLimiter myRateLimiter = new MyRateLimiter();
        myRateLimiter.addTokenFixedRate();
        for (int i = 0; i < 10; i++) {
            myRateLimiter.acquire();
            System.out.println("Execution i:" + i + ", time:" + System.currentTimeMillis());
        }
    }

    /**
     * Periodically add tokens
     */
    public void addTokenFixedRate() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            boolean suc = TOKEN_BUCKET.offer(1);
            if (!suc) {
                System.out.println("Token bucket full, discarding");
            }
        }, 0, 200, TimeUnit.MILLISECONDS);
    }

    public void acquire() {
        while (TOKEN_BUCKET.poll() == null) {}
    }
}

Test results show the implementation meets basic requirements.

RateLimiter Overview Implementation

Examining Guava's RateLimiter source reveals that it does not use a collection as a bucket; instead, it records the next token generation time and the number of stored permits, updating them dynamically.

Overview Logic Diagram

The core Guava code is as follows:

@CanIgnoreReturnValue
public double acquire(int permits) {
  long microsToWait = reserve(permits);
  stopwatch.sleepMicrosUninterruptibly(microsToWait);
  return 1.0 * microsToWait / SECONDS.toMicros(1L);
}

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros;
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  double freshPermits = requiredPermits - storedPermitsToSpend;
  long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
      + (long) (freshPermits * stableIntervalMicros);
  this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}

In summary, RateLimiter does not use a collection as a bucket; its core mechanism tracks the next token generation time and the current number of stored permits, updating them dynamically.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyGuavaToken BucketRateLimiter
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.