A distributed rate limiter implementation using the Token Bucket algorithm and Redis.
This project implements a Token Bucket Rate Limiter using Redis as a backend store. The rate limiter can be used to control the rate of actions (e.g., API requests) performed by users or services in a distributed system.
The Token Bucket algorithm is a widely used rate limiting algorithm that works as follows:
- Each client has a bucket that holds tokens
- Tokens are added to the bucket at a constant rate (the refill rate)
- The bucket has a maximum capacity
- When a client makes a request, a token is removed from the bucket
- If the bucket is empty, the request is denied
- This allows for bursts of traffic up to the bucket capacity, while maintaining a long-term average rate
Redis is used to store the token bucket state, making this rate limiter suitable for distributed environments where multiple application instances need to enforce the same rate limits.
The implementation consists of the following components:
The main class that implements the rate limiting logic:
- Uses Redis transactions to ensure atomicity
- Dynamically calculates token refills based on elapsed time
- Supports multiple clients with separate rate limits
- Thread-safe for concurrent access
- Distributed: Works across multiple application instances
- Accurate: Properly handles time-based token refills
- Efficient: Uses Redis for fast, in-memory operations
- Flexible: Configurable bucket capacity and refill rate
- Thread-safe: Handles concurrent requests correctly
.
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ └── org/
│ │ │ └── example/
│ │ │ └── TokenBucketRateLimiter.java
│ │ └── test/
│ │ └── java/
│ │ └── org/
│ │ └── example/
│ │ ├── TokenBucketRateLimiterIntegrationTest.java
│ └── build.gradle.kts
├── gradle/
├── .gitignore
├── .gitattributes
├── gradle.properties
├── gradlew
├── gradlew.bat
├── run-tests.sh
├── settings.gradle.kts
├── LICENSE
└── README.md
The project includes:
- Integration Tests: Test with a real Redis instance using TestContainers
- Basic rate limiting functionality
- Token refill behavior
- Multiple client handling
- Java 21 or higher
- Gradle (or use the included Gradle wrapper)
- Docker (for running integration tests with TestContainers)
# Clone the repository
git clone https://github.com/yourusername/token-bucket-rate-limiter.git
cd token-bucket-rate-limiter
# Build the project
./gradlew build
Unix/Linux/macOS:
# Make the script executable
chmod +x run-tests.sh
# Run all tests
./run-tests.sh all
# Run integration tests
./run-tests.sh integration
# Build without running tests
./run-tests.sh build
# Show help
./run-tests.sh help
# Run all tests
./gradlew test
# Run integration tests
./gradlew test --tests "org.example.TokenBucketRateLimiterIntegrationTest"
import org.example.TokenBucketRateLimiter;
import redis.clients.jedis.Jedis;
// Create a Jedis client
Jedis jedis = new Jedis("localhost", 6379);
// Create a rate limiter with:
// - bucket capacity of 10 tokens
// - refill rate of 2 tokens per second
TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(jedis, 10, 2);
// Check if a client is allowed to perform an action
String clientId = "user-123";
if (rateLimiter.isAllowed(clientId)) {
// Perform the action
System.out.println("Request allowed");
} else {
// Deny the action
System.out.println("Rate limit exceeded");
}
// Don't forget to close the Jedis client when done
jedis.close();
The TokenBucketRateLimiter
constructor accepts the following parameters:
jedis
: A Redis client instancebucketCapacity
: The maximum number of tokens a bucket can holdrefillRate
: The number of tokens added to the bucket per second
- Redis operations are performed using transactions to ensure atomicity
- The rate limiter calculates token refills based on elapsed time, minimizing Redis operations
- For high-throughput applications, consider using a Redis connection pool
-
Java Not Found: Ensure Java 21 or higher is installed and JAVA_HOME is set correctly.
java -version
-
Docker Not Running: For integration tests, Docker must be running.
docker info
-
Redis Connection Issues: If using an external Redis instance, ensure it's running and accessible.
redis-cli ping
-
Test Failures: If tests fail, check Docker is running and has enough resources allocated.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request