Skip to content

sj1501/distributed-rate-limiter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Distributed Rate Limiter

A distributed rate limiting service backed by Redis, supporting multiple algorithms with atomic Lua script execution. Built with Java 17 and Spring Boot 3.

Architecture

┌─────────────┐     ┌───────────────────────┐     ┌──────────────────────┐
│   Client     │────▶│  RateLimitInterceptor  │────▶│  RateLimiterService  │
│  (HTTP)      │     │  (extracts client ID)  │     │  (orchestrates)      │
└─────────────┘     └───────────────────────┘     └──────────┬───────────┘
       ▲                      │                              │
       │                      │                    ┌─────────▼─────────┐
       │              429 or 200                   │ RateLimitConfig    │
       │              + headers                    │ Service            │
       │                                           └─────────┬─────────┘
       │                                                     │
       │                                           ┌─────────▼─────────┐
       │                                           │ Algorithm Strategy │
       │                                           │ (Token Bucket /    │
       │                                           │  Sliding Window /  │
       │                                           │  Fixed Window)     │
       │                                           └─────────┬─────────┘
       │                                                     │
       │                                              Lua Script
       │                                              (atomic)
       │                                                     │
       │                                           ┌─────────▼─────────┐
       └───────────────────────────────────────────│      Redis         │
                                                   └───────────────────┘

Algorithms

Token Bucket

Best for bursty traffic. Tokens refill at a steady rate. Each request consumes one token. Allows short bursts up to bucket capacity while maintaining an average rate.

  • Redis keys: Hash with tokens and last_refill fields
  • Refill rate: maxRequests / windowSeconds tokens per second
  • Pros: Smooth rate limiting, handles bursts gracefully
  • Cons: Slightly more complex state management

Sliding Window Log

Most precise algorithm. Maintains a log of all request timestamps in the current window using a Redis Sorted Set. Provides exact counting with no boundary issues.

  • Redis keys: Sorted Set (ZSET) with timestamps as scores
  • Pros: No boundary edge cases, exact counts
  • Cons: Higher memory usage (stores each request timestamp)

Fixed Window Counter

Simplest algorithm. Divides time into fixed windows and counts requests per window using a simple counter.

  • Redis keys: String counter with TTL (e.g., key:1711584000)
  • Pros: Minimal memory, simple implementation
  • Cons: Boundary problem -- up to 2x burst at window edges

Why Redis Lua Scripts?

All rate limiting operations use Lua scripts executed atomically on the Redis server. Without atomicity, a race condition exists between "check current count" and "increment count" -- two concurrent requests could both read count=9 (limit=10), both increment, and allow 11 requests through.

Lua scripts execute as a single atomic operation on Redis, eliminating this race condition entirely. This is verified by the concurrency test which fires 20 simultaneous threads and asserts exactly 10 are allowed.

Tech Stack

  • Java 17 + Spring Boot 3.2
  • Redis 7 (distributed state store)
  • Redis Lua scripting (atomic rate limit operations)
  • Docker Compose (local infrastructure)
  • JUnit 5 + Mockito (unit tests)
  • Testcontainers (integration tests with real Redis)

Prerequisites

  • Java 17+
  • Docker and Docker Compose
  • Maven (or use the included wrapper)

Quick Start

# 1. Start Redis
docker-compose up -d

# 2. Build and run
./mvnw spring-boot:run

# 3. Test rate limiting
# Token Bucket: 10 requests/minute on GET /api/resource
for i in {1..12}; do
  echo -n "Request $i: "
  curl -s -o /dev/null -w "%{http_code}" -H "X-Api-Key: demo-client" http://localhost:8080/api/resource
  echo
done
# Requests 1-10: 200, Requests 11-12: 429

# Sliding Window: 3 requests/minute on POST /api/expensive
for i in {1..5}; do
  echo -n "Request $i: "
  curl -s -o /dev/null -w "%{http_code}" -X POST -H "X-Api-Key: demo-client" http://localhost:8080/api/expensive
  echo
done
# Requests 1-3: 200, Requests 4-5: 429

# Fixed Window: 20 requests/minute on GET /api/data
# No rate limit on GET /api/unlimited

Response Headers

All rate-limited responses include:

Header Description
X-RateLimit-Remaining Number of requests remaining in the current window
X-RateLimit-Retry-After Seconds to wait before retrying (only on 429)

Client Identification

Clients are identified by the X-Api-Key header. If no API key is provided, the client's IP address is used as a fallback.

Configuration

Rate limit rules are defined in application.yml:

rate-limit:
  default-algorithm: TOKEN_BUCKET
  rules:
    - endpoint: /api/resource
      method: GET
      max-requests: 10
      window-seconds: 60
      algorithm: TOKEN_BUCKET

Running Tests

# Unit tests only
./mvnw test -Dtest="*Test" -DexcludedGroups=integration

# All tests (requires Docker for Testcontainers)
./mvnw test

Project Structure

src/main/java/com/samviksha/ratelimiter/
├── algorithm/          # Strategy pattern: RateLimitAlgorithm interface + 3 implementations
├── api/                # REST controllers (demo endpoints)
├── config/             # Redis config, rate limit properties binding
├── interceptor/        # Spring HandlerInterceptor for rate limiting
├── model/              # Data models (RateLimitRule, RateLimitResult, AlgorithmType)
└── service/            # Business logic (config resolution, algorithm orchestration)

src/main/resources/
├── scripts/            # Redis Lua scripts (token_bucket, sliding_window, fixed_window)
└── application.yml     # Rate limit rule configuration

Design Decisions

  1. Strategy Pattern for algorithms -- Adding a new algorithm requires implementing one interface and one Lua script. No existing code changes needed (Open/Closed Principle).

  2. Lua scripts over Redis transactions -- MULTI/EXEC doesn't support conditional logic (read-then-write). Lua scripts execute atomically AND support branching.

  3. Interceptor over annotation-based -- Interceptors apply transparently to all /api/** routes based on config. No code changes needed to add rate limiting to a new endpoint.

  4. Config-driven rules -- Rate limits are defined in YAML, not hardcoded. Per-endpoint, per-method granularity with algorithm selection.

  5. Client isolation via key prefix -- Each client gets independent rate limit counters (rate:{clientId}:{method}:{endpoint}), preventing one client from affecting another.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors