mirror of
https://github.com/robindhole/Low-Level-Design.git
synced 2025-09-13 18:52:17 +00:00
Added LLD projects
This commit is contained in:
33
service-orchestrator/src/main/java/LoadBalancer.java
Normal file
33
service-orchestrator/src/main/java/LoadBalancer.java
Normal file
@@ -0,0 +1,33 @@
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
import models.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class LoadBalancer {
|
||||
private final Map<String, Service> services;
|
||||
private final Map<String, Node> nodes;
|
||||
|
||||
public LoadBalancer() {
|
||||
this.services = new ConcurrentHashMap<>();
|
||||
this.nodes = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void register(Service service) {
|
||||
services.put(service.getId(), service);
|
||||
}
|
||||
|
||||
public void addNode(String serviceId, Node node) {
|
||||
nodes.put(node.getId(), node);
|
||||
services.get(serviceId).getRouter().addNode(node);
|
||||
}
|
||||
|
||||
public void removeNode(String serviceId, String nodeId) {
|
||||
services.get(serviceId).getRouter().removeNode(nodes.remove(nodeId));
|
||||
}
|
||||
|
||||
public Node getHandler(Request request) {
|
||||
return services.get(request.getServiceId()).getRouter().getAssignedNode(request);
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package algorithms;
|
||||
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ConsistentHashing implements Router {
|
||||
private final Map<Node, List<Long>> nodePositions;
|
||||
private final ConcurrentSkipListMap<Long, Node> nodeMappings;
|
||||
private final Function<String, Long> hashFunction;
|
||||
private final int pointMultiplier;
|
||||
|
||||
|
||||
public ConsistentHashing(final Function<String, Long> hashFunction,
|
||||
final int pointMultiplier) {
|
||||
if (pointMultiplier == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.pointMultiplier = pointMultiplier;
|
||||
this.hashFunction = hashFunction;
|
||||
this.nodePositions = new ConcurrentHashMap<>();
|
||||
this.nodeMappings = new ConcurrentSkipListMap<>();
|
||||
}
|
||||
|
||||
public void addNode(Node node) {
|
||||
nodePositions.put(node, new CopyOnWriteArrayList<>());
|
||||
for (int i = 0; i < pointMultiplier; i++) {
|
||||
for (int j = 0; j < node.getWeight(); j++) {
|
||||
final var point = hashFunction.apply((i * pointMultiplier + j) + node.getId());
|
||||
nodePositions.get(node).add(point);
|
||||
nodeMappings.put(point, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNode(Node node) {
|
||||
for (final Long point : nodePositions.remove(node)) {
|
||||
nodeMappings.remove(point);
|
||||
}
|
||||
}
|
||||
|
||||
public Node getAssignedNode(Request request) {
|
||||
final var key = hashFunction.apply(request.getId());
|
||||
final var entry = nodeMappings.higherEntry(key);
|
||||
if (entry == null) {
|
||||
return nodeMappings.firstEntry().getValue();
|
||||
} else {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
12
service-orchestrator/src/main/java/algorithms/Router.java
Normal file
12
service-orchestrator/src/main/java/algorithms/Router.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package algorithms;
|
||||
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
|
||||
public interface Router {
|
||||
void addNode(Node node);
|
||||
|
||||
void removeNode(Node node);
|
||||
|
||||
Node getAssignedNode(Request request);
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package algorithms;
|
||||
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WeightedRoundRobin implements Router {
|
||||
private final List<Node> nodes;
|
||||
private int assignTo;
|
||||
private int currentNodeAssignments;
|
||||
private final Object lock;
|
||||
|
||||
public WeightedRoundRobin() {
|
||||
this.nodes = new ArrayList<>();
|
||||
this.assignTo = 0;
|
||||
this.lock = new Object();
|
||||
}
|
||||
|
||||
public void addNode(Node node) {
|
||||
synchronized (this.lock) {
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNode(Node node) {
|
||||
synchronized (this.lock) {
|
||||
nodes.remove(node);
|
||||
assignTo--;
|
||||
currentNodeAssignments = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Node getAssignedNode(Request request) {
|
||||
synchronized (this.lock) {
|
||||
assignTo = assignTo % nodes.size();
|
||||
final var currentNode = nodes.get(assignTo);
|
||||
currentNodeAssignments++;
|
||||
if (currentNodeAssignments == currentNode.getWeight()) {
|
||||
assignTo++;
|
||||
currentNodeAssignments = 0;
|
||||
}
|
||||
return currentNode;
|
||||
}
|
||||
}
|
||||
}
|
45
service-orchestrator/src/main/java/models/Node.java
Normal file
45
service-orchestrator/src/main/java/models/Node.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Node {
|
||||
private final String id;
|
||||
private final int weight;
|
||||
private final String ipAddress;
|
||||
|
||||
public Node(String id, String ipAddress) {
|
||||
this(id, ipAddress, 1);
|
||||
}
|
||||
|
||||
public Node(String id, String ipAddress, int weight) {
|
||||
this.id = id;
|
||||
this.weight = weight;
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Node node = (Node) o;
|
||||
return id.equals(node.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
}
|
26
service-orchestrator/src/main/java/models/Request.java
Normal file
26
service-orchestrator/src/main/java/models/Request.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package models;
|
||||
|
||||
public class Request {
|
||||
private final String id;
|
||||
|
||||
private final String serviceId;
|
||||
private final String method;
|
||||
|
||||
public Request(String id, String serviceId, String method) {
|
||||
this.id = id;
|
||||
this.serviceId = serviceId;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
}
|
27
service-orchestrator/src/main/java/models/Service.java
Normal file
27
service-orchestrator/src/main/java/models/Service.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package models;
|
||||
|
||||
import algorithms.Router;
|
||||
|
||||
public class Service {
|
||||
private final Router router;
|
||||
private final String id;
|
||||
private final String[] methods;
|
||||
|
||||
public Service(String id, Router router, String[] methods) {
|
||||
this.router = router;
|
||||
this.id = id;
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public Router getRouter() {
|
||||
return router;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String[] getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
70
service-orchestrator/src/test/java/LBTester.java
Normal file
70
service-orchestrator/src/test/java/LBTester.java
Normal file
@@ -0,0 +1,70 @@
|
||||
import algorithms.ConsistentHashing;
|
||||
import algorithms.WeightedRoundRobin;
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
import models.Service;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class LBTester {
|
||||
@Test
|
||||
public void LBDefaultBehaviour() {
|
||||
LoadBalancer loadBalancer = new LoadBalancer();
|
||||
final var consistentHashing = new ConsistentHashing(point -> (long) Math.abs(point.hashCode()) % 100, 1);
|
||||
final String profileServiceId = "profile", smsServiceId = "sms", emailServiceId = "email";
|
||||
|
||||
loadBalancer.register(new Service(profileServiceId, consistentHashing, new String[]{"addProfile", "deleteProfile", "updateProfile"}));
|
||||
loadBalancer.register(new Service(smsServiceId, new WeightedRoundRobin(), new String[]{"sendSms", "addTemplate", "getSMSForUser"}));
|
||||
loadBalancer.register(new Service(emailServiceId, new WeightedRoundRobin(), new String[]{"sendEmail", "addTemplate", "getSMSForUser"}));
|
||||
|
||||
final Node pNode1 = new Node("51", "35.45.55.65", 2), pNode2 = new Node("22", "35.45.55.66", 3);
|
||||
loadBalancer.addNode(profileServiceId, pNode1);
|
||||
loadBalancer.addNode(profileServiceId, pNode2);
|
||||
|
||||
final Node sNode1 = new Node("13", "35.45.55.67"), sNode2 = new Node("64", "35.45.55.68");
|
||||
loadBalancer.addNode(smsServiceId, sNode1);
|
||||
loadBalancer.addNode(smsServiceId, sNode2);
|
||||
|
||||
final Node eNode1 = new Node("node-35", "35.45.55.69", 2), eNode2 = new Node("node-76", "35.45.55.70");
|
||||
loadBalancer.addNode(emailServiceId, eNode1);
|
||||
loadBalancer.addNode(emailServiceId, eNode2);
|
||||
|
||||
var profileNode1 = loadBalancer.getHandler(new Request("r-123", profileServiceId, "addProfile"));
|
||||
var profileNode2 = loadBalancer.getHandler(new Request("r-244", profileServiceId, "addProfile"));
|
||||
var profileNode3 = loadBalancer.getHandler(new Request("r-659", profileServiceId, "addProfile"));
|
||||
var profileNode4 = loadBalancer.getHandler(new Request("r-73", profileServiceId, "addProfile"));
|
||||
Assert.assertEquals(pNode1, profileNode1);
|
||||
Assert.assertEquals(pNode1, profileNode2);
|
||||
Assert.assertEquals(pNode2, profileNode3);
|
||||
Assert.assertEquals(pNode1, profileNode4);
|
||||
|
||||
loadBalancer.removeNode(profileServiceId, pNode1.getId());
|
||||
|
||||
profileNode1 = loadBalancer.getHandler(new Request("r-123", profileServiceId, "addProfile"));
|
||||
profileNode2 = loadBalancer.getHandler(new Request("r-244", profileServiceId, "addProfile"));
|
||||
profileNode3 = loadBalancer.getHandler(new Request("r-659", profileServiceId, "addProfile"));
|
||||
profileNode4 = loadBalancer.getHandler(new Request("r-73", profileServiceId, "addProfile"));
|
||||
Assert.assertEquals(pNode2, profileNode1);
|
||||
Assert.assertEquals(pNode2, profileNode2);
|
||||
Assert.assertEquals(pNode2, profileNode3);
|
||||
Assert.assertEquals(pNode2, profileNode4);
|
||||
|
||||
final var smsNode1 = loadBalancer.getHandler(new Request("r-124", smsServiceId, "addTemplate"));
|
||||
final var smsNode2 = loadBalancer.getHandler(new Request("r-1214", smsServiceId, "addTemplate"));
|
||||
final var smsNode3 = loadBalancer.getHandler(new Request("r-4", smsServiceId, "addTemplate"));
|
||||
|
||||
Assert.assertEquals(sNode1, smsNode1);
|
||||
Assert.assertEquals(sNode2, smsNode2);
|
||||
Assert.assertEquals(sNode1, smsNode3);
|
||||
|
||||
final var emailNode1 = loadBalancer.getHandler(new Request("r-1232", emailServiceId, "addTemplate"));
|
||||
final var emailNode2 = loadBalancer.getHandler(new Request("r-4134", emailServiceId, "addTemplate"));
|
||||
final var emailNode3 = loadBalancer.getHandler(new Request("r-23432", emailServiceId, "addTemplate"));
|
||||
final var emailNode4 = loadBalancer.getHandler(new Request("r-5345", emailServiceId, "addTemplate"));
|
||||
|
||||
Assert.assertEquals(eNode1, emailNode1);
|
||||
Assert.assertEquals(eNode1, emailNode2);
|
||||
Assert.assertEquals(eNode2, emailNode3);
|
||||
Assert.assertEquals(eNode1, emailNode4);
|
||||
}
|
||||
}
|
140
service-orchestrator/src/test/java/RouterTester.java
Normal file
140
service-orchestrator/src/test/java/RouterTester.java
Normal file
@@ -0,0 +1,140 @@
|
||||
import algorithms.ConsistentHashing;
|
||||
import algorithms.Router;
|
||||
import algorithms.WeightedRoundRobin;
|
||||
import models.Node;
|
||||
import models.Request;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RouterTester {
|
||||
String ipAddress = "127.0.0.1", serviceId = "service", method = "method";
|
||||
|
||||
@Test
|
||||
public void defaultRoundRobin() {
|
||||
final Router router = new WeightedRoundRobin();
|
||||
final Node node1 = newNode("node-1"), node2 = newNode("node-2"), node3 = newNode("node-3");
|
||||
router.addNode(node1);
|
||||
router.addNode(node2);
|
||||
router.addNode(node3);
|
||||
|
||||
Assert.assertEquals(node1, router.getAssignedNode(newRequest("r-123")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("r-124")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("r-125")));
|
||||
|
||||
router.removeNode(node1);
|
||||
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("r-125")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("r-126")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("r-127")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("r-128")));
|
||||
|
||||
final Node node4 = new Node("node-4", ipAddress, 2);
|
||||
router.addNode(node4);
|
||||
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("r-129")));
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("r-130")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("r-131")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultConsistentHashing() {
|
||||
final List<Long> hashes = new ArrayList<>();
|
||||
hashes.add(1L);
|
||||
hashes.add(11L);
|
||||
hashes.add(21L);
|
||||
hashes.add(31L);
|
||||
final Function<String, Long> hashFunction = id -> {
|
||||
if (id.contains("000000")) {
|
||||
return hashes.remove(0);
|
||||
} else {
|
||||
return Long.parseLong(id);
|
||||
}
|
||||
};
|
||||
final Router router = new ConsistentHashing(hashFunction, 1);
|
||||
final Node node1 = newNode("1000000"), node2 = newNode("2000000"), node3 = newNode("3000000");
|
||||
router.addNode(node1);
|
||||
router.addNode(node2);
|
||||
router.addNode(node3);
|
||||
|
||||
Assert.assertEquals(node1, router.getAssignedNode(newRequest("35")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("5")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("15")));
|
||||
|
||||
router.removeNode(node1);
|
||||
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("22")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("12")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("23")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("13")));
|
||||
|
||||
final Node node4 = newNode("4000000");
|
||||
router.addNode(node4);
|
||||
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("25")));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void consistentHashingConstruction() {
|
||||
new ConsistentHashing(Long::valueOf, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consistentHashingWithWeights() {
|
||||
final List<Long> hashes = new ArrayList<>();
|
||||
hashes.add(1L); // remaining is node 1
|
||||
hashes.add(21L); // 12 to 21 is for node 1
|
||||
hashes.add(11L); // 2 to 11 is for node 2
|
||||
hashes.add(41L); // 32 to 41 is for node 2
|
||||
hashes.add(31L); // 22 to 31 is for node 3
|
||||
hashes.add(51L); // 42 to 51 is for node 3 --> 10 points
|
||||
final Function<String, Long> hashFunction = id -> {
|
||||
//range should be (0, 60)
|
||||
if (id.contains("000000")) {
|
||||
return hashes.remove(0);
|
||||
} else {
|
||||
return Long.parseLong(id);
|
||||
}
|
||||
};
|
||||
final Router router = new ConsistentHashing(hashFunction, 2);
|
||||
final Node node1 = newNode("1000000"), node2 = newNode("2000000"), node3 = newNode("3000000");
|
||||
router.addNode(node1);
|
||||
router.addNode(node2);
|
||||
router.addNode(node3);
|
||||
|
||||
Assert.assertEquals(node1, router.getAssignedNode(newRequest("55")));
|
||||
Assert.assertEquals(node1, router.getAssignedNode(newRequest("15")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("8")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("33")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("28")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("47")));
|
||||
|
||||
router.removeNode(node1);
|
||||
// remaining is node 2
|
||||
// 12 to 21 is now for node 3
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("58")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("12")));
|
||||
Assert.assertEquals(node3, router.getAssignedNode(newRequest("23")));
|
||||
Assert.assertEquals(node2, router.getAssignedNode(newRequest("54")));
|
||||
|
||||
final Node node4 = newNode("4000000");
|
||||
hashes.add(6L); // 0 to 6 is for node 4, 52 to remaining is for node 4
|
||||
hashes.add(26L); // 12 to 26 is for node 4
|
||||
router.addNode(node4);
|
||||
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("15")));
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("59")));
|
||||
Assert.assertEquals(node4, router.getAssignedNode(newRequest("5")));
|
||||
}
|
||||
|
||||
private Request newRequest(String s) {
|
||||
return new Request(s, serviceId, method);
|
||||
}
|
||||
|
||||
private Node newNode(String s) {
|
||||
return new Node(s, ipAddress);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user