diff --git a/BasicCache.java b/BasicCache.java index 57efcaf..ca4047c 100644 --- a/BasicCache.java +++ b/BasicCache.java @@ -32,6 +32,12 @@ public BasicCache() this(50); } + public void clear() + { + map.clear(); + list.clear(); + } + public String get(String key) { // retrieve the value from the map diff --git a/CacheServer.java b/CacheServer.java new file mode 100644 index 0000000..98ff13d --- /dev/null +++ b/CacheServer.java @@ -0,0 +1,32 @@ +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; + +/** + * RMI wrapper for a cache server + */ +public class CacheServer extends UnicastRemoteObject implements ICache +{ + private static final long serialVersionUID = 1L; + + final ICache localCache; + + public CacheServer(ICache localCache) throws RemoteException + { + this.localCache = localCache; + } + + public void clear() + { + localCache.clear(); + } + + public String get(String key) + { + return localCache.get(key); + } + + public void set(String key, String val) + { + localCache.set(key, val); + } +} diff --git a/HashBalancer.java b/HashBalancer.java new file mode 100644 index 0000000..67a3a84 --- /dev/null +++ b/HashBalancer.java @@ -0,0 +1,86 @@ +import java.math.BigInteger; +import java.security.*; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; + +/** + * Manages a set of caching servers and load balances them using key hashing + * @author Roan Kattouw + */ +public class HashBalancer { + protected Vector servers; + + /** + * Constructor + * @param servers Vector of server names + */ + public HashBalancer(Vector servers) { + this.servers = new Vector(servers); + } + + /** + * Constructor + * @param servers Array of server names + */ + public HashBalancer(String[] servers) { + this.servers = new Vector(Arrays.asList(servers)); + } + + /** + * Get a list of all servers managed by this object + * @return Vector of server names + */ + public Vector getServers() { + return servers; + } + + /** + * Get the number of servers managed by this object. + */ + public int getNumberOfServers() { + return servers.size(); + } + + /** + * Add a server to be managed by this object + * @param srv Server name + */ + public void addServer(String srv) { + servers.add(srv); + } + + /** + * Remove a server from this object + * @param srv Server name + */ + public void removeServer(String srv) { + servers.remove(srv); + } + + /** + * Find out which server should have a given key. This is the + * hash-based load balancing part + * @param key Cache key + * @return Server name + */ + public String getServerForKey(String key) { + return servers.get(getIndexForKey(key)); + } + + protected int getIndexForKey(String key) { + try { + byte[] byteArr = key.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hash = md.digest(byteArr); + BigInteger h = new BigInteger(1, hash); + return h.mod(BigInteger.valueOf(servers.size())).intValue(); + } catch(Exception e) { + // Java throws some stupid exceptions in case UTF-8 or MD5 + // aren't supported. This is never realistically gonna happen, + // but just in case let's use a randomized balancer in that case. + Random generator = new Random(); + return generator.nextInt(servers.size()); + } + } +} diff --git a/HashBalancerTest.java b/HashBalancerTest.java new file mode 100644 index 0000000..718f93e --- /dev/null +++ b/HashBalancerTest.java @@ -0,0 +1,22 @@ +import junit.framework.TestCase; + + +public class HashBalancerTest extends TestCase { + + public void testGetIndexForKey() { + // Use 16 servers, so the server name is equal to + // the final hex digit of the MD5 hash of the key + String[] servers = {"0", "1", "2", "3", "4", "5", "6", + "7", "8", "9", "a", "b", "c", "d", "e", "f" + }; + + HashBalancer hb = new HashBalancer(servers); + assertEquals("8", hb.getServerForKey("foo")); + assertEquals("2", hb.getServerForKey("bar")); + assertEquals("8", hb.getServerForKey("baz")); + assertEquals("e", hb.getServerForKey("Roan")); + assertEquals("f", hb.getServerForKey("Jan Paul")); + assertEquals("8", hb.getServerForKey("Roy")); + } + +} diff --git a/ICache.java b/ICache.java index 26ff2b3..bd325b2 100644 --- a/ICache.java +++ b/ICache.java @@ -14,4 +14,9 @@ public interface ICache * Store a string in the cache. */ public void set(String key, String val); + + /** + * Clear the cache. + */ + public void clear(); } \ No newline at end of file diff --git a/Main.java b/Main.java new file mode 100644 index 0000000..9398af1 --- /dev/null +++ b/Main.java @@ -0,0 +1,44 @@ +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.Scanner; +import java.util.Vector; + +public class Main +{ + ICache cache; + + /** + * Init using "java Main configfile.txt serverId cacheSize" + * where configfile.txt is a newline separated list of RMI + * paths, serverId is an integer that denotes the current + * instance (by referring to a line in configfile.txt) and + * cacheSize is the size of the local cache. + * + * If anything fails, a basic cache is used that doesn't use + * RMI. + */ + public Main(String[] args) + { + try { + Scanner scanner = new Scanner(new FileInputStream(args[1])); + Vector serverNames = new Vector(); + + while (scanner.hasNextLine()) { + serverNames.add(scanner.nextLine()); + } + + cache = new RemoteCache((String[])serverNames.toArray(), Integer.parseInt(args[2]), new BasicCache(Integer.parseInt(args[3]))); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Using basic cache..."); + cache = new BasicCache(); + } + + // TODO: instantiate HTTP server + } + + public static void main(String[] args) + { + new Main(args); + } +} diff --git a/RemoteCache.java b/RemoteCache.java new file mode 100644 index 0000000..e81f1af --- /dev/null +++ b/RemoteCache.java @@ -0,0 +1,55 @@ +import java.net.MalformedURLException; +import java.rmi.Naming; +import java.rmi.RemoteException; + +public class RemoteCache implements ICache +{ + CacheServer cache; + HashBalancer hb; + + public RemoteCache(String[] serverNames, int localId, ICache localCache) + { + try { + cache = new CacheServer(localCache); + Naming.rebind(serverNames[localId], cache); + hb = new HashBalancer(serverNames); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + public void clear() + { + for (String server : hb.getServers()) + { + try { + ((ICache)Naming.lookup(server)).clear(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public String get(String key) + { + return getCacheServer(key).get(key); + } + + public void set(String key, String val) + { + getCacheServer(key).set(key, val); + } + + private ICache getCacheServer(String key) + { + try { + return (ICache)Naming.lookup(hb.getServerForKey(key)); + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/StubCache.java b/StubCache.java index fb6ea36..e0e1ff8 100644 --- a/StubCache.java +++ b/StubCache.java @@ -5,4 +5,5 @@ public class StubCache implements ICache { public String get(String key) { return null; } public void set(String key, String val) {} + public void clear() {} } \ No newline at end of file