/// <summary>Adds a node to the ring</summary> /// <remarks>This is only called on the master. Data nodes will /// simply reload everything when something changes</remarks> public void AddNode(CacheNode node) { this.ringLock.EnterWriteLock(); try { // Make sure this node isn't already in the ring string nodeName = node.GetName(); if (this.Nodes.ContainsKey(nodeName)) { throw new Exception("Already added node " + nodeName); } this.Nodes.Add(nodeName, node); DetermineNodeLocations(); LookupEndPoints(); } finally { this.ringLock.ExitWriteLock(); } }
/// <summary>Test basic ring functionality.</summary> /// <remarks>Throws an exception or returns false on failure</remarks> public static bool Test() { CacheRing ring = new CacheRing(); CacheNode nodeA = new CacheNode(); nodeA.HostName = "localhost"; nodeA.PortNumber = 1; nodeA.MaxNumBytes = CacheConfig.ParseMaxNumBytes("48Mb"); ring.Nodes.Add(nodeA.GetName(), nodeA); CacheNode nodeB = new CacheNode(); nodeB.HostName = "localhost"; nodeB.PortNumber = 2; nodeB.MaxNumBytes = CacheConfig.ParseMaxNumBytes("12Mb"); ring.Nodes.Add(nodeB.GetName(), nodeB); CacheNode nodeC = new CacheNode(); nodeC.HostName = "localhost"; nodeC.PortNumber = 3; nodeC.MaxNumBytes = CacheConfig.ParseMaxNumBytes("64Mb"); ring.Nodes.Add(nodeC.GetName(), nodeC); // Hard-code some locations so we can make sure objects get assigned // to the correct virtual node. ring.SortedLocations.Add(10, nodeA); nodeA.Locations.Add(10); ring.SortedLocations.Add(-10, nodeB); nodeB.Locations.Add(-10); ring.SortedLocations.Add(20, nodeC); nodeC.Locations.Add(20); ring.SortedLocations.Add(50, nodeA); nodeA.Locations.Add(50); ring.SortedLocations.Add(60, nodeC); nodeC.Locations.Add(60); var node = ring.GetNodeForHash(5); if (node != nodeA) { throw new Exception(string.Format ("Hash 5 should belong to nodeA, not {0}", node.GetName())); } node = ring.GetNodeForHash(int.MaxValue); if (node != nodeB) { throw new Exception(string.Format ("Hash Integer.MAX should belong to nodeB, not {0}", node.GetName())); } node = ring.GetNodeForHash(20); if (node != nodeC) { throw new Exception(string.Format ("Hash 20 should belong to nodeC, not {0}", node.GetName())); } node = ring.GetNodeForHash(25); if (node != nodeA) { throw new Exception(string.Format ("Hash 25 should belong to nodeA, not {0}", node.GetName())); } // Now get rid of those hard coded locations and let the algorithm decide ring.DetermineNodeLocations(); // Make sure the master list and the nodes agree foreach (var n in ring.Nodes.Values) { foreach (var location in n.Locations) { if (!ring.SortedLocations.ContainsKey(location) || ring.SortedLocations[location] != n) { throw new Exception(string.Format ("Location {0} in node {1} not found", location, node.GetName())); } } } foreach (var loc in ring.SortedLocations) { string nodeName = loc.Value.GetName(); if (!ring.Nodes.ContainsKey(nodeName)) { throw new Exception(string.Format ("ring.Nodes missing {0}", nodeName)); } if (!loc.Value.Locations.Contains(loc.Key)) { throw new Exception(string.Format ("node {0} does not have {1}", nodeName, loc.Key)); } } //Console.WriteLine(ring.GetTrace()); // Now let's place a bunch of values and see how many of them change // when we make changes to the node configuration. //Console.WriteLine("About to place objects"); // nodeName => List of hashes Dictionary <string, List <int> > map = new Dictionary <string, List <int> >(); for (int i = 0; i < 100000; i++) { Guid g = Guid.NewGuid(); int hash = CacheHelper.GetConsistentHashCode(g.ToString()); CacheNode n = ring.GetNodeForHash(hash); string nodeName = n.GetName(); List <int> hashes; if (!map.TryGetValue(nodeName, out hashes)) { hashes = new List <int>(); map[nodeName] = hashes; } hashes.Add(hash); } //foreach (var nodeName in map.Keys) //{ // Console.WriteLine("{0} has {1} hashes", // nodeName, map[nodeName].Count); //} //Console.WriteLine("Modifying sizes and replacing objects"); nodeC.MaxNumBytes = CacheConfig.ParseMaxNumBytes("48Mb"); // was 64Mb ring.PopulateNodes(); int numChanged = 0; int numTotal = 0; foreach (var nodeName in map.Keys) { foreach (int hash in map[nodeName]) { numTotal++; CacheNode n = ring.GetNodeForHash(hash); if (!(nodeName.Equals(n.GetName()))) { numChanged++; } } } if (numChanged >= numTotal) { throw new Exception("Number of changed hases >= total number of hashes"); } // TODO - Caclulate an acceptable percentage //Console.WriteLine("{0} hashes changed which node they were assigned to", // numChanged); return(true); }