Skip to content

Commit 7d5f689

Browse files
Service Orchestrator - Low Level Design
1 parent 83fbf09 commit 7d5f689

22 files changed

+506
-0
lines changed

service-orchestrator/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.example</groupId>
8+
<artifactId>service-orchestrator</artifactId>
9+
<version>1.0</version>
10+
<build>
11+
<plugins>
12+
<plugin>
13+
<groupId>org.apache.maven.plugins</groupId>
14+
<artifactId>maven-compiler-plugin</artifactId>
15+
<configuration>
16+
<source>11</source>
17+
<target>11</target>
18+
</configuration>
19+
</plugin>
20+
</plugins>
21+
</build>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>junit</groupId>
26+
<artifactId>junit</artifactId>
27+
<version>4.13.1</version>
28+
<scope>test</scope>
29+
</dependency>
30+
</dependencies>
31+
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
3+
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11">
4+
<output url="file://$MODULE_DIR$/target/classes" />
5+
<output-test url="file://$MODULE_DIR$/target/test-classes" />
6+
<content url="file://$MODULE_DIR$">
7+
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
8+
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
9+
<excludeFolder url="file://$MODULE_DIR$/${project.build.directory}/classes" />
10+
<excludeFolder url="file://$MODULE_DIR$/${project.build.directory}/test-classes" />
11+
<excludeFolder url="file://$MODULE_DIR$/target" />
12+
</content>
13+
<orderEntry type="inheritedJdk" />
14+
<orderEntry type="sourceFolder" forTests="false" />
15+
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13.1" level="project" />
16+
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
17+
</component>
18+
</module>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import models.Node;
2+
import models.Request;
3+
import models.Service;
4+
5+
import java.util.Map;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
public class LoadBalancer {
9+
private final Map<String, Service> services;
10+
private final Map<String, Node> nodes;
11+
12+
public LoadBalancer() {
13+
this.services = new ConcurrentHashMap<>();
14+
this.nodes = new ConcurrentHashMap<>();
15+
}
16+
17+
public void register(Service service) {
18+
services.put(service.getId(), service);
19+
}
20+
21+
public void addNode(String serviceId, Node node) {
22+
nodes.put(node.getId(), node);
23+
services.get(serviceId).getRouter().addNode(node);
24+
}
25+
26+
public void removeNode(String serviceId, String nodeId) {
27+
services.get(serviceId).getRouter().removeNode(nodes.remove(nodeId));
28+
}
29+
30+
public Node getHandler(Request request) {
31+
return services.get(request.getServiceId()).getRouter().getAssignedNode(request);
32+
}
33+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package algorithms;
2+
3+
import models.Node;
4+
import models.Request;
5+
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.concurrent.ConcurrentSkipListMap;
10+
import java.util.concurrent.CopyOnWriteArrayList;
11+
import java.util.function.Function;
12+
13+
public class ConsistentHashing implements Router {
14+
private final Map<Node, List<Long>> nodePositions;
15+
private final ConcurrentSkipListMap<Long, Node> nodeMappings;
16+
private final Function<String, Long> hashFunction;
17+
private final int pointMultiplier;
18+
19+
20+
public ConsistentHashing(final Function<String, Long> hashFunction,
21+
final int pointMultiplier) {
22+
if (pointMultiplier == 0) {
23+
throw new IllegalArgumentException();
24+
}
25+
this.pointMultiplier = pointMultiplier;
26+
this.hashFunction = hashFunction;
27+
this.nodePositions = new ConcurrentHashMap<>();
28+
this.nodeMappings = new ConcurrentSkipListMap<>();
29+
}
30+
31+
public void addNode(Node node) {
32+
nodePositions.put(node, new CopyOnWriteArrayList<>());
33+
for (int i = 0; i < pointMultiplier; i++) {
34+
for (int j = 0; j < node.getWeight(); j++) {
35+
final var point = hashFunction.apply((i * pointMultiplier + j) + node.getId());
36+
nodePositions.get(node).add(point);
37+
nodeMappings.put(point, node);
38+
}
39+
}
40+
}
41+
42+
public void removeNode(Node node) {
43+
for (final Long point : nodePositions.remove(node)) {
44+
nodeMappings.remove(point);
45+
}
46+
}
47+
48+
public Node getAssignedNode(Request request) {
49+
final var key = hashFunction.apply(request.getId());
50+
final var entry = nodeMappings.higherEntry(key);
51+
if (entry == null) {
52+
return nodeMappings.firstEntry().getValue();
53+
} else {
54+
return entry.getValue();
55+
}
56+
}
57+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package algorithms;
2+
3+
import models.Node;
4+
import models.Request;
5+
6+
public interface Router {
7+
void addNode(Node node);
8+
9+
void removeNode(Node node);
10+
11+
Node getAssignedNode(Request request);
12+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package algorithms;
2+
3+
import models.Node;
4+
import models.Request;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
public class WeightedRoundRobin implements Router {
10+
private final List<Node> nodes;
11+
private final Object lock;
12+
private int assignTo;
13+
private int currentNodeAssignments;
14+
15+
public WeightedRoundRobin() {
16+
this.nodes = new ArrayList<>();
17+
this.assignTo = 0;
18+
this.lock = new Object();
19+
}
20+
21+
public void addNode(Node node) {
22+
synchronized (this.lock) {
23+
nodes.add(node);
24+
}
25+
}
26+
27+
public void removeNode(Node node) {
28+
synchronized (this.lock) {
29+
nodes.remove(node);
30+
assignTo--;
31+
currentNodeAssignments = 0;
32+
}
33+
}
34+
35+
public Node getAssignedNode(Request request) {
36+
synchronized (this.lock) {
37+
assignTo = (assignTo + nodes.size()) % nodes.size();
38+
final var currentNode = nodes.get(assignTo);
39+
currentNodeAssignments++;
40+
if (currentNodeAssignments == currentNode.getWeight()) {
41+
assignTo++;
42+
currentNodeAssignments = 0;
43+
}
44+
return currentNode;
45+
}
46+
}
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package models;
2+
3+
import java.util.Objects;
4+
5+
public class Node {
6+
private final String id;
7+
private final int weight;
8+
private final String ipAddress;
9+
10+
public Node(String id, String ipAddress) {
11+
this(id, ipAddress, 1);
12+
}
13+
14+
public Node(String id, String ipAddress, int weight) {
15+
this.id = id;
16+
this.weight = weight;
17+
this.ipAddress = ipAddress;
18+
}
19+
20+
public String getId() {
21+
return id;
22+
}
23+
24+
public int getWeight() {
25+
return weight;
26+
}
27+
28+
public String getIpAddress() {
29+
return ipAddress;
30+
}
31+
32+
@Override
33+
public boolean equals(Object o) {
34+
if (this == o) return true;
35+
if (o == null || getClass() != o.getClass()) return false;
36+
Node node = (Node) o;
37+
return id.equals(node.id);
38+
}
39+
40+
@Override
41+
public int hashCode() {
42+
return Objects.hash(id);
43+
}
44+
45+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package models;
2+
3+
public class Request {
4+
private final String id;
5+
6+
private final String serviceId;
7+
private final String method;
8+
9+
public Request(String id, String serviceId, String method) {
10+
this.id = id;
11+
this.serviceId = serviceId;
12+
this.method = method;
13+
}
14+
15+
public String getId() {
16+
return id;
17+
}
18+
19+
public String getServiceId() {
20+
return serviceId;
21+
}
22+
23+
public String getMethod() {
24+
return method;
25+
}
26+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package models;
2+
3+
import algorithms.Router;
4+
5+
public class Service {
6+
private final Router router;
7+
private final String id;
8+
private final String[] methods;
9+
10+
public Service(String id, Router router, String[] methods) {
11+
this.router = router;
12+
this.id = id;
13+
this.methods = methods;
14+
}
15+
16+
public Router getRouter() {
17+
return router;
18+
}
19+
20+
public String getId() {
21+
return id;
22+
}
23+
24+
public String[] getMethods() {
25+
return methods;
26+
}
27+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import algorithms.ConsistentHashing;
2+
import algorithms.WeightedRoundRobin;
3+
import models.Node;
4+
import models.Request;
5+
import models.Service;
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
9+
public class LBTester {
10+
@Test
11+
public void LBDefaultBehaviour() {
12+
LoadBalancer loadBalancer = new LoadBalancer();
13+
final var consistentHashing = new ConsistentHashing(point -> (long) Math.abs(point.hashCode()) % 100, 1);
14+
final String profileServiceId = "profile", smsServiceId = "sms", emailServiceId = "email";
15+
16+
loadBalancer.register(new Service(profileServiceId, consistentHashing, new String[]{"addProfile", "deleteProfile", "updateProfile"}));
17+
loadBalancer.register(new Service(smsServiceId, new WeightedRoundRobin(), new String[]{"sendSms", "addTemplate", "getSMSForUser"}));
18+
loadBalancer.register(new Service(emailServiceId, new WeightedRoundRobin(), new String[]{"sendEmail", "addTemplate", "getSMSForUser"}));
19+
20+
final Node pNode1 = new Node("51", "35.45.55.65", 2), pNode2 = new Node("22", "35.45.55.66", 3);
21+
loadBalancer.addNode(profileServiceId, pNode1);
22+
loadBalancer.addNode(profileServiceId, pNode2);
23+
24+
final Node sNode1 = new Node("13", "35.45.55.67"), sNode2 = new Node("64", "35.45.55.68");
25+
loadBalancer.addNode(smsServiceId, sNode1);
26+
loadBalancer.addNode(smsServiceId, sNode2);
27+
28+
final Node eNode1 = new Node("node-35", "35.45.55.69", 2), eNode2 = new Node("node-76", "35.45.55.70");
29+
loadBalancer.addNode(emailServiceId, eNode1);
30+
loadBalancer.addNode(emailServiceId, eNode2);
31+
32+
var profileNode1 = loadBalancer.getHandler(new Request("r-123", profileServiceId, "addProfile"));
33+
var profileNode2 = loadBalancer.getHandler(new Request("r-244", profileServiceId, "addProfile"));
34+
var profileNode3 = loadBalancer.getHandler(new Request("r-659", profileServiceId, "addProfile"));
35+
var profileNode4 = loadBalancer.getHandler(new Request("r-73", profileServiceId, "addProfile"));
36+
Assert.assertEquals(pNode1, profileNode1);
37+
Assert.assertEquals(pNode1, profileNode2);
38+
Assert.assertEquals(pNode2, profileNode3);
39+
Assert.assertEquals(pNode1, profileNode4);
40+
41+
loadBalancer.removeNode(profileServiceId, pNode1.getId());
42+
43+
profileNode1 = loadBalancer.getHandler(new Request("r-123", profileServiceId, "addProfile"));
44+
profileNode2 = loadBalancer.getHandler(new Request("r-244", profileServiceId, "addProfile"));
45+
profileNode3 = loadBalancer.getHandler(new Request("r-659", profileServiceId, "addProfile"));
46+
profileNode4 = loadBalancer.getHandler(new Request("r-73", profileServiceId, "addProfile"));
47+
Assert.assertEquals(pNode2, profileNode1);
48+
Assert.assertEquals(pNode2, profileNode2);
49+
Assert.assertEquals(pNode2, profileNode3);
50+
Assert.assertEquals(pNode2, profileNode4);
51+
52+
final var smsNode1 = loadBalancer.getHandler(new Request("r-124", smsServiceId, "addTemplate"));
53+
final var smsNode2 = loadBalancer.getHandler(new Request("r-1214", smsServiceId, "addTemplate"));
54+
final var smsNode3 = loadBalancer.getHandler(new Request("r-4", smsServiceId, "addTemplate"));
55+
56+
Assert.assertEquals(sNode1, smsNode1);
57+
Assert.assertEquals(sNode2, smsNode2);
58+
Assert.assertEquals(sNode1, smsNode3);
59+
60+
final var emailNode1 = loadBalancer.getHandler(new Request("r-1232", emailServiceId, "addTemplate"));
61+
final var emailNode2 = loadBalancer.getHandler(new Request("r-4134", emailServiceId, "addTemplate"));
62+
final var emailNode3 = loadBalancer.getHandler(new Request("r-23432", emailServiceId, "addTemplate"));
63+
final var emailNode4 = loadBalancer.getHandler(new Request("r-5345", emailServiceId, "addTemplate"));
64+
65+
Assert.assertEquals(eNode1, emailNode1);
66+
Assert.assertEquals(eNode1, emailNode2);
67+
Assert.assertEquals(eNode2, emailNode3);
68+
Assert.assertEquals(eNode1, emailNode4);
69+
}
70+
}

0 commit comments

Comments
 (0)