📂
Interview Prep
LLD Questions
LLD Questions
  • LLD Introduction
  • Meeting Scheduler
  • Distributed Cache System
  • Rate limiter
  • Multi-Tier Elevator System
  • MyGate System
  • Uber Sytem LLD
  • Parking Lot System
  • Online book store
  • Library Management System
  • Movie Ticket Booking System
  • Hotel Management System
  • File Storage System
    • Solution 2
  • Chat Application
  • Social Media Platform
  • Notification System
  • Airline Reservation System
  • ATM System
  • E-commerce Website
  • Food Delivery System
  • Shopping Cart System
  • URL Shortener system
  • Chess Tournament System
  • Threading therory
  • OOP questions
Powered by GitBook
On this page

Rate limiter

A rate limiter is used in real-world applications to control the number of requests or actions that a user or system can perform within a certain time period. It is essential for managing load, preventing abuse, ensuring fair use, and maintaining system stability. Below are some of the most common use cases for rate limiters in various applications:

1. API Rate Limiting

  • Preventing Overload: Many services expose APIs to clients (e.g., third-party applications or end-users). A rate limiter ensures that clients don’t make excessive requests in a short time, which could overwhelm the backend system and affect the quality of service.

  • Fair Usage: Rate limiters ensure that all users are treated equally, preventing one user or service from monopolizing resources.

  • Security: Prevents abuse such as brute-force attacks (e.g., password guessing) or denial-of-service (DoS) attacks by limiting the number of requests a malicious user can make within a time window.

  • Example: A weather API might limit users to 100 requests per minute, ensuring that the system doesn't get overloaded with too many requests at once.

2. Authentication and Login Attempts

  • Prevent Brute-Force Attacks: A rate limiter can be applied to authentication systems to prevent attackers from attempting a large number of incorrect passwords in a short time.

  • Security: In combination with CAPTCHA or other protective measures, rate limiting ensures that attackers cannot bombard a login page or authentication endpoint with repeated failed login attempts, thus protecting sensitive data.

  • Example: A login page might allow a maximum of 5 login attempts per user within 10 minutes to prevent brute-force attacks.

3. Transaction Systems

  • Preventing Fraud: In systems such as banking, e-commerce, or gaming, rate limiting can prevent fraud or abuse by limiting how many transactions a user can initiate in a specific time period.

  • Prevention of Resource Exhaustion: Protects financial systems from being overwhelmed by too many requests (e.g., for purchasing goods or transferring funds).

  • Example: A credit card transaction system might limit a user to only 5 transactions within an hour to prevent misuse.

4. Email and Notification Services

  • Prevent Spamming: In services where users can send emails or notifications, rate limiting ensures that users don’t abuse the system by sending too many messages at once.

  • System Stability: Ensures the email servers or notification systems are not overwhelmed by too many requests (e.g., sending promotional emails in bulk).

  • Example: An email service provider like Gmail or Mailchimp might limit users to 500 emails per hour for free-tier users to prevent spam.

5. Search Engines and Crawlers

  • Managing Crawlers: Web scraping and crawling can put heavy loads on web servers. Websites use rate limiters to restrict how often a crawler can request data from the site in a given time frame.

  • Example: A website might limit how frequently a crawler can access a page to avoid server overload and maintain user experience.

6. Resource Allocation (Cloud Services)

  • API Calls to Cloud Resources: Cloud services, such as AWS, Azure, or Google Cloud, often limit the number of API calls or resource requests a user can make in a given time frame to prevent excessive usage and ensure fair allocation of resources.

  • Preventing System Overload: Helps manage load and prevent accidental overconsumption of resources.

  • Example: In a cloud storage service, rate limits are imposed on the number of requests for file uploads, downloads, or storage operations to ensure equitable resource distribution and avoid high billing costs.

7. Gaming and Social Media

  • Fair Play and User Experience: In gaming or social media platforms, rate limiting is often used to prevent spammy behavior or exploitative actions by users (e.g., sending too many messages in a short time).

  • Preventing Abuse: Ensures that users can’t flood other users with too many actions (e.g., messaging, posting) in a short period.

  • Example: A social media platform might limit users to posting a maximum of 5 status updates within 10 minutes to prevent spamming.

8. Content Delivery Networks (CDNs) and Streaming Services

  • Traffic Management: Rate limiters are used to manage traffic loads to servers, especially during peak times (e.g., for video streaming services or large content downloads).

  • Preventing Abuse: Prevents a single user or client from overloading the system with too many simultaneous requests.

  • Example: A CDN service like Cloudflare might apply rate limits to incoming traffic to avoid overloading servers with too many requests, ensuring users get fair access to content.

9. Preventing Abuse in Online Forms and Surveys

  • Form Spamming: In online forms (e.g., contact forms, feedback surveys), rate limiting helps prevent users from submitting the form too many times in quick succession, which could be an indication of spam or misuse.

  • Example: A survey form may restrict the submission of responses to a maximum of 10 per hour from a single IP address to prevent spam responses.

10. Distributed Systems and Microservices

  • Service Protection: In a microservices architecture, rate limiting can be applied between services to prevent one service from overloading another. If one service is experiencing high load, the rate limiter can slow down or throttle requests.

  • Maintaining System Integrity: It helps in keeping different components in a distributed system healthy by balancing traffic and preventing bottlenecks.

  • Example: A payment service within a microservice architecture might rate limit requests to its API to ensure that only a certain number of transactions can be processed within a given time window.


Rate Limiting Techniques

There are several approaches to implement rate limiting, each suitable for different scenarios:

  1. Fixed Window: Requests are allowed up to a fixed number within a fixed time window (e.g., 100 requests per hour). Once the limit is hit, further requests are blocked until the next window starts.

  2. Sliding Window: Similar to fixed window but allows the window to "slide," so requests are tracked within a time window that shifts dynamically. This is more flexible and reduces the "burst" behavior observed in fixed window approaches.

  3. Token Bucket: A token bucket algorithm uses tokens to control the rate at which actions can be performed. Tokens are added to a bucket at a fixed rate, and when the bucket is full, additional requests are blocked until a token becomes available.

  4. Leaky Bucket: Similar to the token bucket but designed to ensure a steady flow of requests. The leaky bucket fills at a constant rate, and requests are processed as long as there is room in the bucket. If the bucket is full, additional requests are discarded.


Conclusion

In summary, rate limiting is a crucial tool for managing traffic, preventing abuse, ensuring fairness, and improving security in real-world applications. It helps protect systems from overuse, abuse, and malicious activities, while also ensuring that users get fair access to resources. From preventing brute-force attacks to managing API usage and system load, rate limiters are essential in building reliable, scalable, and secure systems.

Rate Limiter: Low-Level Design (LLD) with Design Patterns

A Rate Limiter is a system that controls the rate at which users or applications can make requests to a service. It’s often used in API management, network traffic control, and user request handling to avoid abuse, prevent overload, and ensure fair usage.

In this answer, we'll build a Rate Limiter in Java using the Token Bucket Algorithm, and incorporate well-known design patterns to make the solution modular, flexible, and easy to maintain.


Design Overview

1. Token Bucket Algorithm

The Token Bucket Algorithm is one of the most commonly used rate-limiting algorithms. The idea behind this algorithm is:

  • Tokens are added to a bucket at a fixed rate.

  • Each request consumes one token.

  • If the bucket is full, new tokens are discarded.

  • If the bucket is empty, the requests are rejected (rate-limited).

This allows the system to handle bursts of requests (tokens can accumulate in the bucket) but still respects the overall rate limit.

2. Design Patterns Used

  • Strategy Pattern: Allows flexibility to swap different rate-limiting algorithms (e.g., Token Bucket, Leaky Bucket) without changing the code that uses them.

  • Singleton Pattern: Ensures that only a single instance of the rate limiter is used, which is important for maintaining global rate limits.

  • Factory Method Pattern: Provides a way to create rate limiters with different strategies based on the configuration.

  • Observer Pattern: Allows monitoring components to be notified when a request is allowed or denied, useful for logging, metrics, or alerting.


Rate Limiter Java Code Implementation

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.ArrayList;
import java.util.List;

// Step 1: Define the RateLimiterStrategy interface for different rate-limiting algorithms
interface RateLimiterStrategy {
    boolean allowRequest();
}

// Step 2: Implement Token Bucket Strategy
class TokenBucketStrategy implements RateLimiterStrategy {
    private final long maxTokens;
    private final long refillRate;
    private final AtomicLong availableTokens;
    private long lastRefillTimestamp;

    public TokenBucketStrategy(long maxTokens, long refillRate) {
        this.maxTokens = maxTokens;
        this.refillRate = refillRate;
        this.availableTokens = new AtomicLong(maxTokens);
        this.lastRefillTimestamp = System.nanoTime();
    }

    private void refillTokens() {
        long currentTimestamp = System.nanoTime();
        long timeElapsed = currentTimestamp - lastRefillTimestamp;
        long tokensToAdd = timeElapsed / TimeUnit.SECONDS.toNanos(1) * refillRate;
        long newAvailableTokens = Math.min(maxTokens, availableTokens.get() + tokensToAdd);
        availableTokens.set(newAvailableTokens);
        lastRefillTimestamp = currentTimestamp;
    }

    @Override
    public boolean allowRequest() {
        refillTokens();
        long currentTokens = availableTokens.get();
        if (currentTokens > 0) {
            availableTokens.decrementAndGet();
            return true;
        }
        return false;
    }
}

// Step 3: Define the RateLimiter class (Singleton Pattern + Factory Method Pattern)
class RateLimiter {
    private static RateLimiter instance;
    private final RateLimiterStrategy strategy;
    private final List<RateLimiterObserver> observers;

    // Singleton instance getter
    public static synchronized RateLimiter getInstance(RateLimiterStrategy strategy) {
        if (instance == null) {
            instance = new RateLimiter(strategy);
        }
        return instance;
    }

    private RateLimiter(RateLimiterStrategy strategy) {
        this.strategy = strategy;
        this.observers = new ArrayList<>();
    }

    // Add observers
    public void addObserver(RateLimiterObserver observer) {
        observers.add(observer);
    }

    // Notify observers
    private void notifyObservers(boolean requestAllowed) {
        for (RateLimiterObserver observer : observers) {
            observer.update(requestAllowed);
        }
    }

    // Main method to check if a request is allowed
    public boolean allowRequest() {
        boolean allowed = strategy.allowRequest();
        notifyObservers(allowed);
        return allowed;
    }
}

// Step 4: Observer Pattern to track requests
interface RateLimiterObserver {
    void update(boolean requestAllowed);
}

class RateLimiterLogger implements RateLimiterObserver {
    @Override
    public void update(boolean requestAllowed) {
        if (requestAllowed) {
            System.out.println("Request allowed.");
        } else {
            System.out.println("Request denied due to rate limiting.");
        }
    }
}

// Step 5: Factory for creating RateLimiter with different strategies
class RateLimiterFactory {
    public static RateLimiter createRateLimiter(String strategyType, long maxTokens, long refillRate) {
        RateLimiterStrategy strategy = null;
        switch (strategyType) {
            case "TokenBucket":
                strategy = new TokenBucketStrategy(maxTokens, refillRate);
                break;
            // Other strategies like LeakyBucket can be added here
            default:
                throw new IllegalArgumentException("Unknown strategy type: " + strategyType);
        }
        return RateLimiter.getInstance(strategy);
    }
}

// Step 6: Main Method to demonstrate usage
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Create RateLimiter using the factory with TokenBucket strategy
        RateLimiter rateLimiter = RateLimiterFactory.createRateLimiter("TokenBucket", 10, 1);

        // Add an observer to log the request status
        rateLimiter.addObserver(new RateLimiterLogger());

        // Simulate multiple requests
        for (int i = 0; i < 20; i++) {
            if (rateLimiter.allowRequest()) {
                System.out.println("Request " + (i + 1) + " allowed.");
            } else {
                System.out.println("Request " + (i + 1) + " denied.");
            }
            Thread.sleep(100);  // Simulate a small delay between requests
        }
    }
}

Explanation of Design Patterns Used

1. Strategy Pattern:

  • RateLimiterStrategy defines a common interface for different rate-limiting strategies (e.g., Token Bucket, Leaky Bucket, etc.).

  • The TokenBucketStrategy implements this interface to provide rate-limiting logic based on the Token Bucket Algorithm.

  • This pattern allows for flexible changes in rate-limiting strategies without altering the rest of the system.

2. Singleton Pattern:

  • RateLimiter ensures that only a single instance of the rate limiter is created. The singleton pattern provides a global access point to the rate limiter, ensuring consistent enforcement of the rate limit throughout the system.

3. Factory Method Pattern:

  • RateLimiterFactory is used to create instances of rate limiters with different strategies (e.g., Token Bucket, Leaky Bucket). It abstracts the creation logic and allows easy extension to include more strategies in the future.

  • This pattern decouples the creation of the rate limiter from its usage.

4. Observer Pattern:

  • RateLimiterObserver interface allows other parts of the system to be notified when a request is allowed or denied.

  • RateLimiterLogger is an observer that logs the outcome of each request.

  • The RateLimiter class notifies all registered observers whenever a request is processed, helping in tracking, logging, or triggering actions based on the rate-limiting status.


Advantages of This Approach

  1. Modularity: Each rate-limiting strategy is encapsulated in its own class, following the Single Responsibility Principle. New strategies can be added without modifying existing code.

  2. Flexibility: The Strategy Pattern allows the rate-limiting algorithm to be changed or extended easily (e.g., by adding new strategies like Leaky Bucket).

  3. Global Access: The Singleton Pattern ensures there’s a single, globally accessible instance of the rate limiter, preventing conflicting rate-limiting rules across different parts of the system.

  4. Extensibility: With the Factory Method, new rate-limiting strategies can be added by simply extending the RateLimiterStrategy interface and modifying the factory.

  5. Monitoring and Logging: The Observer Pattern allows for external components to monitor the requests processed by the rate limiter, which is useful for tracking, debugging, and alerting.


Example Output

Request 1 allowed.
Request 2 allowed.
Request 3 allowed.
Request 4 allowed.
Request 5 allowed.
Request 6 allowed.
Request 7 allowed.
Request 8 allowed.
Request 9 allowed.
Request 10 allowed.
Request 11 denied.
Request 12 denied.
Request 13 denied.
Request 14 denied.
Request 15 denied.
Request 16 denied.
Request 17 denied.
Request 18 denied.
Request 19 denied.
Request 20 denied.

Conclusion

This approach to designing a Rate Limiter using design patterns makes the system highly flexible, modular, and easy to maintain. The Strategy Pattern allows for different rate-limiting algorithms to be used interchangeably, while the Singleton Pattern ensures consistent rate limiting across the system. The Factory Method simplifies the creation of rate limiters, and the Observer Pattern provides a mechanism to track and log request handling. This solution is ideal for scenarios where scalability, extensibility, and monitoring are critical.

PreviousDistributed Cache SystemNextMulti-Tier Elevator System

Last updated 2 months ago