Added LLD projects

This commit is contained in:
Your Name
2020-07-15 18:02:38 +05:30
commit 0d2fabc962
135 changed files with 3319 additions and 0 deletions

View 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;
}
}

View File

@@ -0,0 +1,7 @@
package exceptions;
public class RateLimitExceededException extends IllegalStateException {
public RateLimitExceededException() {
super("Rate limit exceeded");
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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;
}
}