A thread-safe rate limiter implementation in Rust using Redis as the backend storage. This library provides a simple and efficient way to implement rate limiting in your applications.
- Thread-safe rate limiting using Redis
- Configurable request limits and time windows
- Support for multiple identifiers (e.g., per user, IP, endpoint)
- Atomic operations using Redis Lua scripts
- Built-in methods to check remaining requests and time windows
- Comprehensive test suite
The rate limiter uses a sliding window algorithm implemented with Redis. Here's how it works:
- Each identifier (e.g., user, IP) gets a unique Redis key with the format
{prefix}:{identifier} - The key stores a counter that tracks the number of requests
- The key has a TTL (Time To Live) equal to the rate limit window
- When a request comes in:
- If the key doesn't exist, it's created with a counter of 1 and the window TTL
- If the key exists and the counter is below the limit, the counter is incremented
- If the key exists and the counter has reached the limit, the request is rejected
- If the key has expired (TTL = 0), it's treated as a new key
The implementation uses Redis Lua scripts to ensure atomic operations, preventing race conditions in concurrent scenarios. The script:
- Checks if the key exists
- If it doesn't exist, creates it with initial count of 1
- If it exists, checks if we've hit the limit
- If we haven't hit the limit, increments the counter
- Sets/updates the TTL on the key
This approach provides:
- Accurate rate limiting
- No memory leaks (keys automatically expire)
- Thread-safe operations
- Minimal Redis operations (single atomic script)
Add this to your Cargo.toml:
[dependencies]
redis_rate_limiter = "0.1.0"use redis_rate_limiter::{RateLimiter, RateLimiterError};
use std::time::Duration;
fn main() -> Result<(), RateLimiterError> {
// Create a new rate limiter instance
let limiter = RateLimiter::new(
"redis://127.0.0.1:6379", // Redis URL
"my_app", // Key prefix
100, // Max requests
Duration::from_secs(60), // Time window
)?;
// Check if a request should be allowed
match limiter.check("user_123") {
Ok(_) => println!("Request allowed"),
Err(RateLimiterError::RateLimitExceeded) => println!("Rate limit exceeded"),
Err(e) => println!("Error: {}", e),
}
// Get remaining requests
let remaining = limiter.get_remaining("user_123")?;
println!("Remaining requests: {}", remaining);
// Get time until rate limit resets
let time_remaining = limiter.get_time_remaining("user_123")?;
println!("Time remaining: {} seconds", time_remaining);
Ok(())
}The main struct that handles rate limiting operations.
-
new(redis_url: &str, key_prefix: &str, max_requests: u64, window: Duration) -> Result<Self, RateLimiterError>- Creates a new rate limiter instance
redis_url: URL of the Redis serverkey_prefix: Prefix for Redis keysmax_requests: Maximum number of requests allowed in the time windowwindow: Duration of the time window
-
check(identifier: &str) -> Result<(), RateLimiterError>- Checks if a request should be allowed
- Returns
Ok(())if the request is allowed - Returns
Err(RateLimiterError::RateLimitExceeded)if the rate limit is exceeded
-
get_remaining(identifier: &str) -> Result<u64, RateLimiterError>- Returns the number of remaining requests for the given identifier
-
get_time_remaining(identifier: &str) -> Result<i64, RateLimiterError>- Returns the time remaining until the rate limit resets (in seconds)
- Returns -1 if the key has expired or doesn't exist
Error type for rate limiter operations.
pub enum RateLimiterError {
Redis(redis::RedisError),
RateLimitExceeded,
}- Redis server (version 2.6 or later)
- Rust 1.70 or later
The library includes a comprehensive test suite. To run the tests:
cargo testNote: Tests require a running Redis server at redis://127.0.0.1:6379. You can use Docker to run Redis:
docker run -d -p 6379:6379 redisThis project is licensed under the MIT License - see the LICENSE file for details.