RateLimit class - Implementation based on Sliding Window Algorithm that maintains the map of requests at given timestamp and the counter for that (similar to Redis Hash)

import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Implementation of Sliding window algorithm with timestamp and counter
 * (example: redis hash)
 */
public class RateLimit {

    int rateLimit;
    Map<String, LinkedList<Request>> userRequestMap = new ConcurrentHashMap<>();

    public RateLimit(int limit) {
        this.rateLimit = limit;
    }

    /**
     * Thread safe block being invoked by multiple threads
     * @param user username
     * @param timestamp timestamp of request
     * @return request allowed true/false
     */
    public synchronized boolean hit(String user, Instant timestamp) {

        if (!userRequestMap.containsKey(user)) {
            return addNewUser(user);
        } else {
            if (getTotalElpasedRequests(user) < rateLimit) {
                LinkedList<Request> requests = userRequestMap.get(user);
                requests.add(new Request(timestamp, 1));
                userRequestMap.put(user, requests);
                return true;
            } else {
                boolean actionTaken = false;
                for (int i = 0; i < userRequestMap.get(user).size(); i++) {
                    Duration duration = Duration.between(userRequestMap.get(user).get(i).getTimestamp(), timestamp);
					// check for elapsed time greater than 1 minute (60 seconds)
					// This can be passed as an argument at runtime to avoid hardcoding
                    if (duration.getSeconds() >= 60) {
                        userRequestMap.get(user).remove(i);
                        actionTaken = true;
                    } else {
                        break;
                    }
                }

                if (actionTaken) {
                    LinkedList<Request> requests = userRequestMap.get(user);
                    requests.add(new Request(timestamp, 1));
                    userRequestMap.put(user, requests);
                    return true;
                }

                return false;
            }
        }
    }

    public boolean addNewUser(String user) {
        LinkedList<Request> requests = new LinkedList<>();
        requests.add(new Request(Instant.now(), 1));
        userRequestMap.put(user, requests);
        System.out.println("New User added !! " + user);
        return true;
    }

    public Integer getTotalElpasedRequests(String user) {
        return userRequestMap.get(user).stream().mapToInt(Request::getCount).sum();
    }

}

Thread Helper class - Invoke multiple threads to check throttling of requests

import java.time.Instant;

public class RateLimitHelper extends Thread {

    RateLimit rateLimit;

    public RateLimitHelper(String user, RateLimit rateLimitService) {
        super(user);
        this.rateLimit = rateLimitService;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 65; i++) {
            System.out.println("Thread Name - " + getName() + ", Time - " + i + ", rate limit: " + hit(getName(), Instant.now()));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("DONE! " + getName());
    }

    public boolean hit(String user, Instant ts) {
        return rateLimit.hit(user, ts);
    }
}

Request class - wrapper for Timestamp and count of request for a User

import java.time.Instant;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Request {

    private Instant timestamp;
    private Integer count;

    public Request(Instant timestamp, Integer count) {
        this.timestamp = timestamp;
        this.count = count;
    }
}

Main class

public class RateLimiterService {

    public static void main(String[] args) {
        RateLimiterService rateLimiterService = new RateLimiterService();
        int limit = 5; // 5 requests per minute
        RateLimit rateLimit = new RateLimit(limit);
        new RateLimitHelper("UserA", rateLimit).start();
        new RateLimitHelper("userB", rateLimit).start();
    }

}

Output:

New User added !! UserA
New User added !! userB
Thread Name - UserA, Time - 1, rate limit: true
Thread Name - userB, Time - 1, rate limit: true
Thread Name - userB, Time - 2, rate limit: true
Thread Name - UserA, Time - 2, rate limit: true
Thread Name - UserA, Time - 3, rate limit: true
Thread Name - userB, Time - 3, rate limit: true
Thread Name - UserA, Time - 4, rate limit: true
Thread Name - userB, Time - 4, rate limit: true
Thread Name - UserA, Time - 5, rate limit: true
Thread Name - userB, Time - 5, rate limit: true
Thread Name - userB, Time - 6, rate limit: false
Thread Name - UserA, Time - 6, rate limit: false
Thread Name - userB, Time - 7, rate limit: false
Thread Name - UserA, Time - 7, rate limit: false
.
.
.
.
.
Comments (14)