Commit b68b0ad7 authored by drnull03's avatar drnull03

Added report and added code modification for solving weaknesses in this implementation

parent 339eb5eb
......@@ -16,9 +16,9 @@ import java.nio.charset.StandardCharsets;
*/
public class ConsistentHash<T> {
private static final int DEFAULT_VIRTUAL_NODES = 150;
private final SortedMap<Long, T> ring = new TreeMap<>();
private final int virtualNodes;
private final HashFunction hashFunction;
protected final SortedMap<Long, T> ring = new TreeMap<>();
protected final int virtualNodes;
protected final HashFunction hashFunction;
/**
* Create a ConsistentHash with default number of virtual nodes
......@@ -90,6 +90,8 @@ public class ConsistentHash<T> {
return tailMap.get(tailMap.firstKey());
}
//ask a qesution here
// Wrap around: return the first node in the ring
return ring.get(ring.firstKey());
}
......
package org.ds;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Implementation of the "Multiple Hash Functions" solution from the slide.
* Maps each node to the ring using distinct mathematical algorithms.
*/
public class MultiAlgorithmConsistentHash<T> extends ConsistentHash<T> {
private final List<String> algorithms = Arrays.asList("MD5", "SHA-1", "SHA-256");
public MultiAlgorithmConsistentHash(int virtualNodes) {
super(virtualNodes);
}
@Override
public void addNode(T node) {
// As per the slide: Apply multiple hash functions on each node
for (int i = 0; i < virtualNodes; i++) {
// Cycle through algorithms (MD5, SHA1, SHA256) for each virtual node
String algo = algorithms.get(i % algorithms.size());
long hash = calculateHash(node.toString() + ":" + i, algo);
// Put directly into the protected ring
ring.put(hash, node);
}
}
@Override
public void removeNode(T node) {
for (int i = 0; i < virtualNodes; i++) {
String algo = algorithms.get(i % algorithms.size());
long hash = calculateHash(node.toString() + ":" + i, algo);
ring.remove(hash);
}
}
private long calculateHash(String key, String algorithm) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] bytes = md.digest(key.getBytes(StandardCharsets.UTF_8));
long hash = 0;
// Use 8 bytes for a 64-bit long hash
for (int i = 0; i < Math.min(bytes.length, 8); i++) {
hash = (hash << 8) | (bytes[i] & 0xFF);
}
return Math.abs(hash);
} catch (Exception e) {
throw new RuntimeException("Error calculating " + algorithm, e);
}
}
}
\ No newline at end of file
......@@ -74,6 +74,18 @@ public class TestRunner {
testCompleteLifecycle();
testCacheClusterScenario();
testLoadBalancingScenario();
//added tests
//multihash algo tests
testMultiAlgorithmFunctionality();
testMultiAlgorithmBalance();
//weighted tests go here
testWeightedDistribution();
}
// ============================================
......@@ -596,6 +608,85 @@ public class TestRunner {
}
}
// ============================================
// MULTI-ALGORITHM EXTENSION TESTS
// ============================================
static void testMultiAlgorithmFunctionality() {
try {
System.out.println(" Testing Multiple Hash Function extension...");
MultiAlgorithmConsistentHash<String> multi = new MultiAlgorithmConsistentHash<>(30);
multi.addNode("server-1");
assertEquals(30, multi.getRingSize(), "Should have 30 points on the ring");
assertNotNull(multi.getNode("test-key"), "Should successfully route a key");
multi.removeNode("server-1");
assertEquals(0, multi.getRingSize(), "Ring should be empty after removal");
pass("testMultiAlgorithmFunctionality");
} catch (Exception e) {
fail("testMultiAlgorithmFunctionality", e);
}
}
static void testMultiAlgorithmBalance() {
try {
System.out.println(" Testing load balance with multiple algorithms...");
MultiAlgorithmConsistentHash<String> multi = new MultiAlgorithmConsistentHash<>(300);
multi.addNode("A");
multi.addNode("B");
multi.addNode("C");
Map<String, Integer> dist = multi.getDistribution(10000);
// Check that all nodes got roughly 33% (statistically even)
for (String node : Arrays.asList("A", "B", "C")) {
int count = dist.get(node);
assertTrue(count > 2500 && count < 4000,
"Node " + node + " distribution (" + count + ") is uneven");
}
pass("testMultiAlgorithmBalance");
} catch (Exception e) {
fail("testMultiAlgorithmBalance", e);
}
}
// ============================================
// WEIGHTED DISTRIBUTION TESTS
// ============================================
static void testWeightedDistribution() {
try {
System.out.println("\n┌─ WEIGHTED CAPACITY TESTS ────────────────────────────────────────┐");
// Base VN is 50
WeightedConsistentHash<String> weightedHash = new WeightedConsistentHash<>(50);
System.out.println("Adding 'Small-Server' (Weight 1) and 'Giant-Server' (Weight 10)...");
weightedHash.addNode("Small-Server", 1); // Gets 50 virtual nodes
weightedHash.addNode("Giant-Server", 10); // Gets 500 virtual nodes
// Run distribution check
int sampleSize = 10000;
Map<String, Integer> dist = weightedHash.getDistribution(sampleSize);
int smallCount = dist.get("Small-Server");
int giantCount = dist.get("Giant-Server");
double ratio = (double) giantCount / smallCount;
System.out.printf(" Small Server keys: %d\n", smallCount);
System.out.printf(" Giant Server keys: %d\n", giantCount);
System.out.printf(" Measured Ratio: %.2fx (Target: ~10x)\n", ratio);
// Verify Giant server took significantly more (at least 7x to account for hash variance)
assertTrue(ratio > 7.0, "Giant server should have significantly more load");
pass("testWeightedDistribution");
} catch (Exception e) {
fail("testWeightedDistribution", e);
}
}
// ============================================
// Assertion Methods
// ============================================
......
package org.ds;
import java.util.*;
/**
* Extension: Implements "Heterogeneous Node Weighting".
* Allows servers with more CPU/RAM to handle a larger share of the ring.
*/
public class WeightedConsistentHash<T> extends ConsistentHash<T> {
// Store weights to ensure consistent removal
private final Map<T, Integer> nodeWeights = new HashMap<>();
public WeightedConsistentHash(int baseVirtualNodes) {
super(baseVirtualNodes);
}
/**
* Add a node with a specific capacity weight.
* @param node The physical node
* @param weight Capacity multiplier (e.g., 1 for small, 5 for large)
*/
public void addNode(T node, int weight) {
if (weight <= 0) weight = 1;
nodeWeights.put(node, weight);
// The "Secret Sauce": Total Virtual Nodes = Base VN * Weight
int totalVirtualNodesForThisNode = virtualNodes * weight;
for (int i = 0; i < totalVirtualNodesForThisNode; i++) {
long hash = hashFunction.hash(node.toString() + ":" + i);
ring.put(hash, node);
}
}
/**
* Standard addNode defaults to weight 1
*/
@Override
public void addNode(T node) {
addNode(node, 1);
}
@Override
public void removeNode(T node) {
Integer weight = nodeWeights.getOrDefault(node, 1);
int totalVirtualNodes = virtualNodes * weight;
for (int i = 0; i < totalVirtualNodes; i++) {
long hash = hashFunction.hash(node.toString() + ":" + i);
ring.remove(hash);
}
nodeWeights.remove(node);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment