mirror of
https://github.com/robindhole/Low-Level-Design.git
synced 2025-09-13 19:12:20 +00:00
Added LLD projects
This commit is contained in:
77
rate-limiter/src/main/java/TimerWheel.java
Normal file
77
rate-limiter/src/main/java/TimerWheel.java
Normal file
@@ -0,0 +1,77 @@
|
||||
import exceptions.RateLimitExceededException;
|
||||
import models.Request;
|
||||
import utils.Timer;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class TimerWheel {
|
||||
private final int timeOutPeriod;
|
||||
private final int capacityPerSlot;
|
||||
private final TimeUnit timeUnit;
|
||||
private final ArrayBlockingQueue<Request>[] slots;
|
||||
private final Map<String, Integer> reverseIndex;
|
||||
private final Timer timer;
|
||||
private final ExecutorService[] threads;
|
||||
|
||||
public TimerWheel(final TimeUnit timeUnit,
|
||||
final int timeOutPeriod,
|
||||
final int capacityPerSlot,
|
||||
final Timer timer) {
|
||||
this.timeUnit = timeUnit;
|
||||
this.timeOutPeriod = timeOutPeriod;
|
||||
this.capacityPerSlot = capacityPerSlot;
|
||||
if (this.timeOutPeriod > 1000) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.slots = new ArrayBlockingQueue[this.timeOutPeriod];
|
||||
this.threads = new ExecutorService[this.timeOutPeriod];
|
||||
this.reverseIndex = new ConcurrentHashMap<>();
|
||||
for (int i = 0; i < slots.length; i++) {
|
||||
slots[i] = new ArrayBlockingQueue<>(capacityPerSlot);
|
||||
threads[i] = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
this.timer = timer;
|
||||
final long timePerSlot = TimeUnit.MILLISECONDS.convert(1, timeUnit);
|
||||
Executors.newSingleThreadScheduledExecutor()
|
||||
.scheduleAtFixedRate(this::flushRequests,
|
||||
timePerSlot - (this.timer.getCurrentTimeInMillis() % timePerSlot),
|
||||
timePerSlot, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public Future<?> flushRequests() {
|
||||
final int currentSlot = getCurrentSlot();
|
||||
return threads[currentSlot].submit(() -> {
|
||||
for (final Request request : slots[currentSlot]) {
|
||||
if (timer.getCurrentTime(timeUnit) - request.getStartTime() >= timeOutPeriod) {
|
||||
slots[currentSlot].remove(request);
|
||||
reverseIndex.remove(request.getRequestId());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Future<?> addRequest(final Request request) {
|
||||
final int currentSlot = getCurrentSlot();
|
||||
return threads[currentSlot].submit(() -> {
|
||||
if (slots[currentSlot].size() >= capacityPerSlot) {
|
||||
throw new RateLimitExceededException();
|
||||
}
|
||||
slots[currentSlot].add(request);
|
||||
reverseIndex.put(request.getRequestId(), currentSlot);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<?> evict(final String requestId) {
|
||||
final int currentSlot = reverseIndex.get(requestId);
|
||||
return threads[currentSlot].submit(() -> {
|
||||
slots[currentSlot].remove(new Request(requestId, 0));
|
||||
reverseIndex.remove(requestId);
|
||||
});
|
||||
}
|
||||
|
||||
private int getCurrentSlot() {
|
||||
return (int) timer.getCurrentTime(timeUnit) % slots.length;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
package exceptions;
|
||||
|
||||
public class RateLimitExceededException extends IllegalStateException {
|
||||
public RateLimitExceededException() {
|
||||
super("Rate limit exceeded");
|
||||
}
|
||||
}
|
33
rate-limiter/src/main/java/models/Request.java
Normal file
33
rate-limiter/src/main/java/models/Request.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Request {
|
||||
private final String requestId;
|
||||
private final long startTime;
|
||||
|
||||
public Request(String requestId, long startTime) {
|
||||
this.requestId = requestId;
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
return requestId.equals(((Request) o).requestId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return requestId.hashCode();
|
||||
}
|
||||
}
|
13
rate-limiter/src/main/java/utils/Timer.java
Normal file
13
rate-limiter/src/main/java/utils/Timer.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package utils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Timer {
|
||||
public long getCurrentTime(final TimeUnit timeUnit) {
|
||||
return timeUnit.convert(getCurrentTimeInMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public long getCurrentTimeInMillis() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
74
rate-limiter/src/test/java/RateLimitTest.java
Normal file
74
rate-limiter/src/test/java/RateLimitTest.java
Normal file
@@ -0,0 +1,74 @@
|
||||
import models.Request;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RateLimitTest {
|
||||
|
||||
@Test
|
||||
public void testDefaultBehaviour() throws Exception {
|
||||
final TimeUnit timeUnit = TimeUnit.SECONDS;
|
||||
final TestTimer timer = new TestTimer();
|
||||
final TimerWheel timerWheel = new TimerWheel(timeUnit, 6, 3, timer);
|
||||
timerWheel.addRequest(new Request("1", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("2", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("3", timer.getCurrentTime(timeUnit))).get();
|
||||
Throwable exception = null;
|
||||
try {
|
||||
timerWheel.addRequest(new Request("4", timer.getCurrentTime(timeUnit))).get();
|
||||
} catch (Exception e) {
|
||||
exception = e.getCause();
|
||||
}
|
||||
Assert.assertNotNull(exception);
|
||||
Assert.assertEquals("Rate limit exceeded", exception.getMessage());
|
||||
tick(timeUnit, timer, timerWheel);
|
||||
timerWheel.addRequest(new Request("4", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("5", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.evict("1").get();
|
||||
timerWheel.evict("4").get();
|
||||
timerWheel.addRequest(new Request("6", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("7", timer.getCurrentTime(timeUnit))).get();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearing() throws Exception {
|
||||
final TimeUnit timeUnit = TimeUnit.SECONDS;
|
||||
final TestTimer timer = new TestTimer();
|
||||
final int timeOutPeriod = 6;
|
||||
final TimerWheel timerWheel = new TimerWheel(timeUnit, timeOutPeriod, 3, timer);
|
||||
timerWheel.addRequest(new Request("0", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("1", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("2", timer.getCurrentTime(timeUnit))).get();
|
||||
|
||||
Throwable exception = null;
|
||||
try {
|
||||
timerWheel.addRequest(new Request("3", timer.getCurrentTime(timeUnit))).get();
|
||||
} catch (Exception e) {
|
||||
exception = e.getCause();
|
||||
}
|
||||
Assert.assertNotNull(exception);
|
||||
Assert.assertEquals("Rate limit exceeded", exception.getMessage());
|
||||
|
||||
for (int i = 0; i < timeOutPeriod; i++) {
|
||||
tick(timeUnit, timer, timerWheel);
|
||||
}
|
||||
timerWheel.addRequest(new Request("4", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("5", timer.getCurrentTime(timeUnit))).get();
|
||||
timerWheel.addRequest(new Request("6", timer.getCurrentTime(timeUnit))).get();
|
||||
|
||||
exception = null;
|
||||
try {
|
||||
timerWheel.addRequest(new Request("7", timer.getCurrentTime(timeUnit))).get();
|
||||
} catch (Exception e) {
|
||||
exception = e.getCause();
|
||||
}
|
||||
Assert.assertNotNull(exception);
|
||||
Assert.assertEquals("Rate limit exceeded", exception.getMessage());
|
||||
}
|
||||
|
||||
private void tick(TimeUnit timeUnit, TestTimer timer, TimerWheel timerWheel) throws Exception {
|
||||
timer.setTime(timer.getCurrentTimeInMillis() + TimeUnit.MILLISECONDS.convert(1, timeUnit));
|
||||
timerWheel.flushRequests().get();
|
||||
}
|
||||
}
|
14
rate-limiter/src/test/java/TestTimer.java
Normal file
14
rate-limiter/src/test/java/TestTimer.java
Normal file
@@ -0,0 +1,14 @@
|
||||
import utils.Timer;
|
||||
|
||||
public class TestTimer extends Timer {
|
||||
private long currentTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public long getCurrentTimeInMillis() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
public void setTime(final long currentTime) {
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user