Skip to content

Repository files navigation

Limitly — Redis-Backed Rate Limiter Spring Boot Starter

Java Version Spring Boot License

Limitly is a production-grade, zero-config, annotation-based rate limiting library built for Spring Boot applications powered by Redis. Built with Java 21, Spring Boot 3, and Lua Scripting to ensure race-condition-free, atomic operations across distributed pod replicas.


⚡ Features

  • Annotation-based DX: Drop @RateLimit on any @RestController endpoint.
  • Atomic Redis Execution: Zero race conditions under high concurrent traffic using Redis Lua scripts (EVALSHA/EVAL).
  • Dual Algorithms Supported:
    • Token Bucket: Smoothed traffic flow, configurable refill rates, low Redis memory footprint.
    • Sliding Window Log: Exact request-count precision using Redis Sorted Sets (ZSET).
  • Flexible Key Resolution:
    • USER: Resolves user Principal or X-User-ID header.
    • IP: Resolves real client IP (aware of X-Forwarded-For proxy headers).
    • SPEL: Dynamic SpEL expression support (e.g. #request.remoteAddr, #user.id).
  • Resilient Fallback Modes:
    • FAIL_OPEN: (Default) Allows request traffic through when Redis is unreachable.
    • FAIL_CLOSED: Throttles requests securely during Redis outages.
  • Production Observability: Built-in Micrometer metrics (ratelimiter.requests.allowed, ratelimiter.requests.denied, ratelimiter.redis.latency, ratelimiter.redis.failures) ready for Prometheus and Grafana dashboards.
  • RFC-compliant HTTP Headers: Automatically appends X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After HTTP headers on throttled 429 responses.

🏗️ High-Level Architecture

┌───────────────────────────────────────────────────────────┐
│                     Client Request                        │
└────────────────────────┬──────────────────────────────────┘
                         ▼
              ┌─────────────────────┐
              │ RateLimitInterceptor│  (HandlerInterceptor)
              └──────────┬──────────┘
                         │ resolves key via KeyResolver
                         ▼
              ┌─────────────────────┐
              │  RateLimiterService │
              └──────────┬──────────┘
                         │ executes Lua script atomically
                         ▼
              ┌─────────────────────┐
              │   Redis (Lua exec)  │
              └──────────┬──────────┘
                         │ Returns allowed/remaining/retryAfter
                         ▼
             ┌───────────────────────────┐
             │ Allowed: Continue (200)   │
             │ Denied: Return 429 + Retry│
             └───────────────────────────┘

🚀 Quick Start

1. Add Dependency

Add the starter dependency to your pom.xml:

<dependency>
    <groupId>io.github.tharunprabu</groupId>
    <artifactId>redis-rate-limiter-spring-boot-starter</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

2. Configure application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379

rate-limiter:
  enabled: true
  default-strategy: SLIDING_WINDOW
  default-key-resolver: USER
  fallback-mode: FAIL_OPEN
  redis:
    key-prefix: "ratelimit:"
  response:
    status: 429
    body: '{"error": "rate_limit_exceeded", "message": "Too many requests. Please try again later."}'
    include-retry-after-header: true
  metrics:
    enabled: true

3. Annotate Endpoints

@RestController
@RequestMapping("/api")
public class ProductController {

    // 5 requests per 10 seconds using Sliding Window Log
    @GetMapping("/products/search")
    @RateLimit(limit = 5, window = "10s", strategy = Algorithm.SLIDING_WINDOW)
    public ResponseEntity<?> search(@RequestParam String query) {
        return ResponseEntity.ok(Map.of("query", query));
    }

    // 10 requests per 1 minute based on IP address using Token Bucket algorithm
    @PostMapping("/ads")
    @RateLimit(key = "#request.remoteAddr", limit = 10, window = "1m", strategy = Algorithm.TOKEN_BUCKET)
    public ResponseEntity<?> createAd(HttpServletRequest request) {
        return ResponseEntity.ok("Ad created");
    }

    // Fail closed on critical resource if Redis connection drops
    @GetMapping("/critical")
    @RateLimit(limit = 2, window = "5s", onRedisFailure = FallbackMode.FAIL_CLOSED)
    public ResponseEntity<?> criticalEndpoint() {
        return ResponseEntity.ok("Access granted");
    }
}

📊 Comparison: Token Bucket vs Sliding Window Log

Feature Token Bucket Sliding Window Log
Data Structure Redis Hash (HMSET) Redis Sorted Set (ZSET)
Memory Footprint Extremely low (~2 fields per key) Proportional to request volume in window
Accuracy Continuous refill rate, allows micro-bursts 100% exact request count precision
Best Used For General API endpoints, bulk processing Strict financial APIs, authentication/login attempts

📈 Observability & Micrometer Metrics

When Micrometer and Spring Actuator are present, Limitly automatically records the following metrics:

  • ratelimiter.requests.allowed (Counter, tagged with key, route)
  • ratelimiter.requests.denied (Counter, tagged with key, route)
  • ratelimiter.redis.latency (Timer measuring Redis Lua script execution duration)
  • ratelimiter.redis.failures (Counter measuring Redis downtime fallback events)

🛠️ Project Structure

limitly/
├── redis-rate-limiter-core/                   # Core engine & Lua scripts (Zero Spring dependencies)
├── redis-rate-limiter-spring-boot-starter/    # Auto-Configuration, Interceptors, SpEL, Metrics
└── redis-rate-limiter-samples/demo-app/       # Executable Spring Boot sample application

📜 License

Distributed under the Apache 2.0 License. Built by Tharun prabu.

About

Production-grade Redis-backed rate limiter Spring Boot starter. Features atomic Lua execution, Token Bucket & Sliding Window algorithms, SpEL resolution, and Micrometer metrics.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages