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
.
.
.
.
.