add consistent hashing implementations
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
package implementations.java.consistent_hashing;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ConsistentHashing {
|
||||||
|
private final int numReplicas; // Number of virtual nodes per server
|
||||||
|
private final TreeMap<Long, String> ring; // Hash ring storing virtual nodes
|
||||||
|
private final Set<String> servers; // Set of physical servers
|
||||||
|
|
||||||
|
public ConsistentHashing(List<String> servers, int numReplicas) {
|
||||||
|
this.numReplicas = numReplicas;
|
||||||
|
this.ring = new TreeMap<>();
|
||||||
|
this.servers = new HashSet<>();
|
||||||
|
|
||||||
|
// Add each server to the hash ring
|
||||||
|
for (String server : servers) {
|
||||||
|
addServer(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long hash(String key) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(key.getBytes());
|
||||||
|
byte[] digest = md.digest();
|
||||||
|
return ((long) (digest[0] & 0xFF) << 24) |
|
||||||
|
((long) (digest[1] & 0xFF) << 16) |
|
||||||
|
((long) (digest[2] & 0xFF) << 8) |
|
||||||
|
((long) (digest[3] & 0xFF));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("MD5 algorithm not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addServer(String server) {
|
||||||
|
servers.add(server);
|
||||||
|
for (int i = 0; i < numReplicas; i++) {
|
||||||
|
long hash = hash(server + "-" + i); // Unique hash for each virtual node
|
||||||
|
ring.put(hash, server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeServer(String server) {
|
||||||
|
if (servers.remove(server)) {
|
||||||
|
for (int i = 0; i < numReplicas; i++) {
|
||||||
|
long hash = hash(server + "-" + i);
|
||||||
|
ring.remove(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServer(String key) {
|
||||||
|
if (ring.isEmpty()) {
|
||||||
|
return null; // No servers available
|
||||||
|
}
|
||||||
|
|
||||||
|
long hash = hash(key);
|
||||||
|
// Find the closest server in a clockwise direction
|
||||||
|
Map.Entry<Long, String> entry = ring.ceilingEntry(hash);
|
||||||
|
if (entry == null) {
|
||||||
|
// If we exceed the highest node, wrap around to the first node
|
||||||
|
entry = ring.firstEntry();
|
||||||
|
}
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> servers = Arrays.asList("S0", "S1", "S2", "S3", "S4", "S5");
|
||||||
|
ConsistentHashing ch = new ConsistentHashing(servers, 3);
|
||||||
|
|
||||||
|
// Step 2: Assign requests (keys) to servers
|
||||||
|
System.out.println("UserA is assigned to: " + ch.getServer("UserA"));
|
||||||
|
System.out.println("UserB is assigned to: " + ch.getServer("UserB"));
|
||||||
|
|
||||||
|
// Step 3: Add a new server dynamically
|
||||||
|
ch.addServer("S6");
|
||||||
|
System.out.println("UserA is now assigned to: " + ch.getServer("UserA"));
|
||||||
|
|
||||||
|
// Step 4: Remove a server dynamically
|
||||||
|
ch.removeServer("S2");
|
||||||
|
System.out.println("UserB is now assigned to: " + ch.getServer("UserB"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import hashlib
|
||||||
|
import bisect
|
||||||
|
|
||||||
|
class ConsistentHashing:
|
||||||
|
def __init__(self, servers, num_replicas=3):
|
||||||
|
"""
|
||||||
|
Initializes the consistent hashing ring.
|
||||||
|
|
||||||
|
- servers: List of initial server names (e.g., ["S0", "S1", "S2"])
|
||||||
|
- num_replicas: Number of virtual nodes per server for better load balancing
|
||||||
|
"""
|
||||||
|
self.num_replicas = num_replicas # Number of virtual nodes per server
|
||||||
|
self.ring = {} # Hash ring storing virtual node mappings
|
||||||
|
self.sorted_keys = [] # Sorted list of hash values (positions) on the ring
|
||||||
|
self.servers = set() # Set of physical servers (used for tracking)
|
||||||
|
|
||||||
|
# Add each server to the hash ring
|
||||||
|
for server in servers:
|
||||||
|
self.add_server(server)
|
||||||
|
|
||||||
|
def _hash(self, key):
|
||||||
|
"""Computes a hash value for a given key using MD5."""
|
||||||
|
return int(hashlib.md5(key.encode()).hexdigest(), 16)
|
||||||
|
|
||||||
|
def add_server(self, server):
|
||||||
|
"""
|
||||||
|
Adds a server to the hash ring along with its virtual nodes.
|
||||||
|
|
||||||
|
- Each virtual node is a different hash of the server ID to distribute load.
|
||||||
|
- The server is hashed multiple times and placed at different positions.
|
||||||
|
"""
|
||||||
|
self.servers.add(server)
|
||||||
|
for i in range(self.num_replicas): # Creating multiple virtual nodes
|
||||||
|
hash_val = self._hash(f"{server}-{i}") # Unique hash for each virtual node
|
||||||
|
self.ring[hash_val] = server # Map hash to the server
|
||||||
|
bisect.insort(self.sorted_keys, hash_val) # Maintain a sorted list for efficient lookup
|
||||||
|
|
||||||
|
def remove_server(self, server):
|
||||||
|
"""
|
||||||
|
Removes a server and all its virtual nodes from the hash ring.
|
||||||
|
"""
|
||||||
|
if server in self.servers:
|
||||||
|
self.servers.remove(server)
|
||||||
|
for i in range(self.num_replicas):
|
||||||
|
hash_val = self._hash(f"{server}-{i}") # Remove each virtual node's hash
|
||||||
|
self.ring.pop(hash_val, None) # Delete from hash ring
|
||||||
|
self.sorted_keys.remove(hash_val) # Remove from sorted key list
|
||||||
|
|
||||||
|
def get_server(self, key):
|
||||||
|
"""
|
||||||
|
Finds the closest server for a given key.
|
||||||
|
|
||||||
|
- Hash the key to get its position on the ring.
|
||||||
|
- Move clockwise to find the nearest server.
|
||||||
|
- If it exceeds the last node, wrap around to the first node.
|
||||||
|
"""
|
||||||
|
if not self.ring:
|
||||||
|
return None # No servers available
|
||||||
|
|
||||||
|
hash_val = self._hash(key) # Hash the key
|
||||||
|
index = bisect.bisect(self.sorted_keys, hash_val) % len(self.sorted_keys) # Locate nearest server
|
||||||
|
return self.ring[self.sorted_keys[index]] # Return the assigned server
|
||||||
|
|
||||||
|
# ----------------- Usage Example -------------------
|
||||||
|
|
||||||
|
# Step 1: Initialize Consistent Hashing with servers
|
||||||
|
servers = ["S0", "S1", "S2", "S3", "S4", "S5"]
|
||||||
|
ch = ConsistentHashing(servers)
|
||||||
|
|
||||||
|
# Step 2: Assign requests (keys) to servers
|
||||||
|
print(ch.get_server("UserA")) # Maps UserA to a server
|
||||||
|
print(ch.get_server("UserB")) # Maps UserB to a server
|
||||||
|
|
||||||
|
# Step 3: Add a new server dynamically
|
||||||
|
ch.add_server("S6")
|
||||||
|
print(ch.get_server("UserA")) # Might be reassigned if affected
|
||||||
|
|
||||||
|
# Step 4: Remove a server dynamically
|
||||||
|
ch.remove_server("S2")
|
||||||
|
print(ch.get_server("UserB")) # Might be reassigned if affected
|
||||||
Reference in New Issue
Block a user